Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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: 3 additions & 3 deletions .github/workflows/create_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ jobs:
VERSION='${{ inputs.version }}'
INCR='${{ inputs.increment }}'
if [[ "$MODE" == "explicit" && -z "$VERSION" ]]; then
echo " mode=explicit requires 'version' (e.g., 1.3-alpha)."; exit 1
echo "? mode=explicit requires 'version' (e.g., 1.3-alpha)."; exit 1
fi
if [[ "$MODE" == "bump" && -z "$INCR" ]]; then
echo " mode=bump requires 'increment' (major|minor|patch)."; exit 1
echo "? mode=bump requires 'increment' (major|minor|patch)."; exit 1
fi
echo " inputs look good."
echo "? inputs look good."

set-version:
needs: validate-inputs
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
branches: [main, develop]

workflow_run:
workflows: [Create Prerelease, Create Release]
workflows: [Create Release]
types: [requested]

permissions:
Expand Down
19 changes: 14 additions & 5 deletions .github/workflows/pack_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ permissions:
contents: write
pull-requests: write
packages: write
statuses: write
statuses: write

jobs:
set-config:
uses: Stillpoint-Software/shared-workflows/.github/workflows/determine_build_configuration.yml@main
Expand All @@ -18,17 +18,26 @@ jobs:
target_branch: ${{ github.event.release.target_commitish }}
override_build_configuration: ''
prerelease: ${{ github.event.release.prerelease }} # true/false from the release

publish:
tests:
needs: set-config
uses: Stillpoint-Software/shared-workflows/.github/workflows/run_tests.yml@main
with:
branch: ${{ github.event.release.target_commitish }}
solution_name: ${{ vars.SOLUTION_NAME }}

publish:
needs: [set-config, tests]
uses: Stillpoint-Software/shared-workflows/.github/workflows/pack_and_publish.yml@main
with:
build_configuration: ${{ needs.set-config.outputs.build_configuration }}
secrets:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}

result:
needs: [publish]
needs: [publish, tests]
if: always()
runs-on: ubuntu-latest
steps:
- run: echo "Tests result = ${{ needs.tests.result }}"
- run: echo "Pack & Publish result = ${{ needs.publish.result }}"
2 changes: 1 addition & 1 deletion .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Run Tests

on:
workflow_run:
workflows: [Create Prerelease, Create Release]
workflows: [Create Release]
types: [requested]
branches: [main, develop]
workflow_dispatch:
Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -395,4 +395,10 @@ FodyWeavers.xsd
*.msp

# JetBrains Rider
*.sln.iml
*.sln.iml

# Claude AI assistant files
.claude/
claude.md
**/claude-tasks.md
**/baseline-benchmarks.md
8 changes: 4 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageIcon>assets/icon.png</PackageIcon>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/PostgresTest/releases/latest</PackageReleaseNotes>
<RepositoryUrl>https://github.com/Stillpoint-Software/PostgresTest</RepositoryUrl>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/hyperbee.json/releases/latest</PackageReleaseNotes>
<RepositoryUrl>https://github.com/Stillpoint-Software/hyperbee.json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/Stillpoint-Software/PostgresTest</PackageProjectUrl>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
</PropertyGroup>

<!-- Pull README & LICENSE into the package root -->
Expand All @@ -42,7 +42,7 @@
PackagePath="\"
Link="LICENSE" />
</ItemGroup>
<!-- Global project properies -->
<!-- Global project properties - .NET 10 LTS First Strategy -->
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Primary target: .NET 10 (next LTS), with .NET 9 for current support, .NET 8 for compatibility -->
Expand Down
1 change: 1 addition & 0 deletions Hyperbee.Json.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="__" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=2c62818f_002D621b_002D4425_002Dadc9_002D78611099bfcb/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"&gt;&lt;ElementKinds&gt;&lt;Kind Name="TYPE_PARAMETER" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="Aa_bb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=53eecf85_002Dd821_002D40e8_002Dac97_002Dfdb734542b84/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=70345118_002D4b40_002D4ece_002D937c_002Dbbeb7a0b2e70/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
Expand Down
15 changes: 15 additions & 0 deletions NuGet.Config
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>

<!-- Package source mapping for Central Package Management -->
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="Microsoft.*" />
<package pattern="System.*" />
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
4 changes: 0 additions & 4 deletions docs/.todo.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/docs.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<Import_RootNamespace>docs</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory).todo.md" />
<None Include="$(MSBuildThisFileDirectory)additional-classes.md" />
<None Include="$(MSBuildThisFileDirectory)index.md" />
<None Include="$(MSBuildThisFileDirectory)jsonpatch.md" />
Expand Down
6 changes: 3 additions & 3 deletions src/Hyperbee.Json/Dynamic/DynamicJsonElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out o
return true;
}

result = null;
result = null!;
return false;
}

Expand All @@ -60,7 +60,7 @@ public override bool TryGetMember( GetMemberBinder binder, out object result )
}
}

result = null;
result = null!;
return false;
}

Expand All @@ -74,7 +74,7 @@ public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args,
return true;
}

result = null;
result = null!;
return false;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Hyperbee.Json/Dynamic/DynamicJsonNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override bool TryGetIndex( GetIndexBinder binder, object[] indexes, out o
return true;
}

result = null;
result = null!;
return false;
}

Expand All @@ -57,7 +57,7 @@ public override bool TryGetMember( GetMemberBinder binder, out object result )
result = new DynamicJsonNode( ref arrayValue );
return true;
default:
result = null;
result = null!;
return false;
}
}
Expand Down Expand Up @@ -86,7 +86,7 @@ public override bool TryInvokeMember( InvokeMemberBinder binder, object[] args,
return true;
}

result = null;
result = null!;
return false;
}

Expand Down
35 changes: 17 additions & 18 deletions src/Hyperbee.Json/Hyperbee.Json.csproj
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<Authors>Stillpoint Software, Inc.</Authors>
<PackageId>Hyperbee.Json</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>json-path;jsonpath;json-pointer;jsonpointer;json-patch;jsonpatch;query;path;patch;diff;json;rfc9535;rfc6901;rfc6902</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>Stillpoint Software, Inc.</Copyright>
<Title>Hyperbee Json</Title>
<Description>A high-performance JSON library for System.Text.Json JsonElement and JsonNode, providing robust support for JSONPath, JsonPointer, JsonPatch, and JsonDiff.</Description>
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/Hyperbee.Json/releases/latest</PackageReleaseNotes>

</PropertyGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<Authors>Stillpoint Software, Inc.</Authors>
<PackageId>Hyperbee.Json</PackageId>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>json-path;jsonpath;json-pointer;jsonpointer;json-patch;jsonpatch;query;path;patch;diff;json;rfc9535;rfc6901;rfc6902</PackageTags>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://stillpoint-software.github.io/hyperbee.json/</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Copyright>Stillpoint Software, Inc.</Copyright>
<Title>Hyperbee Json</Title>
<Description>A high-performance JSON library for System.Text.Json JsonElement and JsonNode, providing robust support for JSONPath, JsonPointer, JsonPatch, and JsonDiff.</Description>
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Json</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReleaseNotes>https://github.com/Stillpoint-Software/Hyperbee.Json/releases/latest</PackageReleaseNotes>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class FunctionExpressionFactory : IExpressionFactory
public static bool TryGetExpression<TNode>( ref ParserState state, out Expression expression, out CompareConstraint compareConstraint, ITypeDescriptor<TNode> descriptor )
{
compareConstraint = CompareConstraint.None;
expression = null;
expression = null!;

if ( state.Item.IsEmpty || !char.IsLetter( state.Item[0] ) )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( !TryParseNode( descriptor.NodeActions, state.Item, out var node ) )
{
expression = null;
expression = null!;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class NotExpressionFactory : IExpressionFactory
public static bool TryGetExpression<TNode>( ref ParserState state, out Expression expression, out CompareConstraint compareConstraint, ITypeDescriptor<TNode> _ = null )
{
compareConstraint = CompareConstraint.None;
expression = null;
expression = null!;

return state.Operator == Operator.Not;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( state.Operator != Operator.OpenParen || !state.Item.IsEmpty )
{
expression = null;
expression = null!;
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static bool TryGetExpression<TNode>( ref ParserState state, out Expressio

if ( item.IsEmpty || item[0] != '$' && item[0] != '@' )
{
expression = null;
expression = null!;
return false;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Hyperbee.Json/Path/Filters/Parser/ExtensionFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected ExtensionFunction( MethodInfo methodInfo, CompareConstraint compareCon
internal Expression GetExpression<TNode>( ref ParserState state )
{
var arguments = new Expression[_argumentCount];
var expectNormalized = CompareConstraint.HasFlag( CompareConstraint.ExpectNormalized );
var expectNormalized = (CompareConstraint & CompareConstraint.ExpectNormalized) != 0;

for ( var i = 0; i < _argumentCount; i++ )
{
Expand Down
34 changes: 26 additions & 8 deletions src/Hyperbee.Json/Path/Filters/Parser/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ internal static Expression Parse( ref ParserState state ) // recursion entrypoin
{
MoveNext( ref state );
items.Enqueue( GetExprItem( ref state ) ); // may cause recursion

} while ( state.IsParsing );

// check for paren mismatch
Expand Down Expand Up @@ -153,11 +152,24 @@ static bool IsFinished( in ParserState state, char ch, ref int itemEnd )
private static void MoveNextOperator( ref ParserState state ) // move to the next operator
{
if ( state.Operator.IsLogical() || state.Operator.IsComparison() || state.Operator.IsMath() )
{
return;
}

if ( !state.IsParsing )
// Determine if we should stop looking for an operator.
//
// When IsParsing is false (Previous == TerminalCharacter), we've hit a potential stopping point.
// However, ')' as a terminal character is ambiguous - it could be:
// 1. A function's closing paren: `length(@.x)` - should continue to find `> 10` in `(length(@.x) > 10)`
// 2. The outer expression's closing paren - should stop
// 3. A function argument's closing paren - should stop
//
// We return early (stop) when:
// - TerminalCharacter is not ')' (e.g., ',' is unambiguous), OR
// - ParenDepth == 0 (we're at the outermost level, so ')' closes the expression), OR
// - IsArgument is true (we're parsing a function argument, so ')' is definitely ours)
//
// Otherwise, we fall through to the while loop to continue scanning for operators.

if ( !state.IsParsing && (state.TerminalCharacter != ArgClose || state.ParenDepth == 0 || state.IsArgument) )
{
state.Operator = Operator.NonOperator;
return;
Expand All @@ -174,6 +186,7 @@ private static void MoveNextOperator( ref ParserState state ) // move to the nex

private static void NextCharacter( ref ParserState state, int start, out char nextChar, ref char? quoteChar )
{
// Read next character
nextChar = state.Buffer[state.Pos++];

// Handle escape characters within quotes
Expand Down Expand Up @@ -438,25 +451,30 @@ private static void ThrowIfInvalidCompare( in ParserState state, ExprItem left,

private static void ThrowIfLiteralInvalidCompare( in ParserState state, ExprItem left, ExprItem right )
{
const CompareConstraint literalMustCompare = CompareConstraint.Literal | CompareConstraint.MustCompare;

if ( state.IsArgument || left.Operator.IsMath() )
return;

if ( left.CompareConstraint.HasFlag( CompareConstraint.Literal | CompareConstraint.MustCompare ) && !left.Operator.IsComparison() )
if ( (left.CompareConstraint & literalMustCompare) == literalMustCompare && !left.Operator.IsComparison() )
throw new NotSupportedException( $"Unsupported literal without comparison: {state.Buffer.ToString()}." );

if ( right != null && right.CompareConstraint.HasFlag( CompareConstraint.Literal | CompareConstraint.MustCompare ) && !left.Operator.IsComparison() )
if ( right != null && (right.CompareConstraint & literalMustCompare) == literalMustCompare && !left.Operator.IsComparison() )
throw new NotSupportedException( $"Unsupported literal without comparison: {state.Buffer.ToString()}." );
}

private static void ThrowIfFunctionInvalidCompare( in ParserState state, ExprItem item )
{
const CompareConstraint functionMustCompare = CompareConstraint.Function | CompareConstraint.MustCompare;
const CompareConstraint functionMustNotCompare = CompareConstraint.Function | CompareConstraint.MustNotCompare;

if ( state.IsArgument )
return;

if ( item.CompareConstraint.HasFlag( CompareConstraint.Function | CompareConstraint.MustCompare ) && !item.Operator.IsComparison() )
if ( (item.CompareConstraint & functionMustCompare) == functionMustCompare && !item.Operator.IsComparison() )
throw new NotSupportedException( $"Function must compare: {state.Buffer.ToString()}." );

if ( item.CompareConstraint.HasFlag( CompareConstraint.Function | CompareConstraint.MustNotCompare ) && item.Operator.IsComparison() )
if ( (item.CompareConstraint & functionMustNotCompare) == functionMustNotCompare && item.Operator.IsComparison() )
throw new NotSupportedException( $"Function must not compare: {state.Buffer.ToString()}." );
}

Expand Down
9 changes: 5 additions & 4 deletions src/Hyperbee.Json/Path/Filters/Parser/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ public enum Operator

internal static class OperatorExtensions
{
public static bool IsNonOperator( this Operator op ) => op.HasFlag( Operator.NonOperator );
public static bool IsComparison( this Operator op ) => op.HasFlag( Operator.Comparison );
public static bool IsLogical( this Operator op ) => op.HasFlag( Operator.Logical );
public static bool IsMath( this Operator op ) => op.HasFlag( Operator.Math );
// Use direct bitwise operations instead of HasFlag() to avoid boxing allocations
public static bool IsNonOperator( this Operator op ) => (op & Operator.NonOperator) != 0;
public static bool IsComparison( this Operator op ) => (op & Operator.Comparison) != 0;
public static bool IsLogical( this Operator op ) => (op & Operator.Logical) != 0;
public static bool IsMath( this Operator op ) => (op & Operator.Math) != 0;
}
Loading