Skip to content

Instantly share code, notes, and snippets.

@reed-lawrence
Created February 18, 2026 02:59
Show Gist options
  • Select an option

  • Save reed-lawrence/a4b544c1581f8afd8740191b601701f4 to your computer and use it in GitHub Desktop.

Select an option

Save reed-lawrence/a4b544c1581f8afd8740191b601701f4 to your computer and use it in GitHub Desktop.
Abstract HTTP request AST, translator, and evaluator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
public sealed class RequestAbstract
{
public string Path { get; init; } = "";
public string Method { get; init; } = "";
public IReadOnlyDictionary<string, string> Headers { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public IReadOnlyDictionary<string, string> QueryParameters { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public IReadOnlyDictionary<string, string> PathParameters { get; init; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public JsonPathProxy JsonPath { get; } = new();
public PathTemplateProxy PathTemplate { get; } = new();
}
public sealed class JsonPathProxy
{
public string this[string path] => throw new NotSupportedException();
public JsonPathIntProxy Int => new();
public JsonPathDecimalProxy Decimal => new();
public JsonPathBoolProxy Bool => new();
}
public sealed class JsonPathIntProxy
{
public int this[string path] => throw new NotSupportedException();
}
public sealed class JsonPathDecimalProxy
{
public decimal this[string path] => throw new NotSupportedException();
}
public sealed class JsonPathBoolProxy
{
public bool this[string path] => throw new NotSupportedException();
}
public sealed class PathTemplateProxy
{
public bool this[string template] => throw new NotSupportedException();
}
// ------------------ AST ------------------
public abstract record FilterNode;
public sealed record AndNode(FilterNode Left, FilterNode Right) : FilterNode;
public sealed record OrNode(FilterNode Left, FilterNode Right) : FilterNode;
public sealed record NotNode(FilterNode Inner) : FilterNode;
public sealed record FieldRef(string Kind, string Name, string? SubKey = null);
public enum CompareOp
{
Eq, Neq, Gt, Gte, Lt, Lte,
Contains, StartsWith, EndsWith,
In, Exists, Regex
}
public sealed record CompareNode(
CompareOp Op,
FieldRef Field,
string? Value = null,
string? ValueType = null,
StringComparison? Comparison = null
) : FilterNode;
public sealed record PathTemplateNode(string Template, bool CaseSensitive = false) : FilterNode;
// ------------------ Translator ------------------
public static class V2ExpressionTranslator
{
public static FilterNode Translate(Expression<Func<RequestAbstract, bool>> expr)
=> TranslateExpr(expr.Body);
private static FilterNode TranslateExpr(Expression expr)
=> expr switch
{
BinaryExpression b when b.NodeType == ExpressionType.AndAlso
=> new AndNode(TranslateExpr(b.Left), TranslateExpr(b.Right)),
BinaryExpression b when b.NodeType == ExpressionType.OrElse
=> new OrNode(TranslateExpr(b.Left), TranslateExpr(b.Right)),
BinaryExpression b when b.NodeType == ExpressionType.Equal
=> TranslateCompare(b.Left, b.Right, CompareOp.Eq),
BinaryExpression b when b.NodeType == ExpressionType.NotEqual
=> TranslateCompare(b.Left, b.Right, CompareOp.Neq),
BinaryExpression b when b.NodeType == ExpressionType.GreaterThan
=> TranslateCompare(b.Left, b.Right, CompareOp.Gt),
BinaryExpression b when b.NodeType == ExpressionType.GreaterThanOrEqual
=> TranslateCompare(b.Left, b.Right, CompareOp.Gte),
BinaryExpression b when b.NodeType == ExpressionType.LessThan
=> TranslateCompare(b.Left, b.Right, CompareOp.Lt),
BinaryExpression b when b.NodeType == ExpressionType.LessThanOrEqual
=> TranslateCompare(b.Left, b.Right, CompareOp.Lte),
UnaryExpression u when u.NodeType == ExpressionType.Not
=> new NotNode(TranslateExpr(u.Operand)),
MethodCallExpression m when IsStringMethod(m, out var op, out var comparison)
=> TranslateStringMethod(m, op, comparison),
MethodCallExpression m when IsRegexIsMatch(m)
=> TranslateRegex(m),
MethodCallExpression m when IsContainsKey(m, out var dictName, out var key)
=> new CompareNode(CompareOp.Exists, new FieldRef("dict", dictName, key)),
MethodCallExpression m when IsDictIndexer(m, out var dictName2, out var key2)
=> new CompareNode(CompareOp.Exists, new FieldRef("dict", dictName2, key2)),
_ => throw new NotSupportedException($"Unsupported expression: {expr}")
};
private static FilterNode TranslateCompare(Expression left, Expression right, CompareOp op)
{
if (!TryGetField(left, out var field, out var valueType))
{
if (TryGetField(right, out field, out valueType))
return TranslateCompare(right, left, op);
throw new NotSupportedException("Comparison must include a supported field.");
}
var value = ExtractConstantAsString(right);
return new CompareNode(op, field, value, valueType);
}
private static FilterNode TranslateStringMethod(MethodCallExpression m, CompareOp op, StringComparison comparison)
{
if (!TryGetField(m.Object!, out var field, out var valueType))
throw new NotSupportedException("String method must be called on a supported field.");
var value = ExtractConstantAsString(m.Arguments[0]);
return new CompareNode(op, field, value, valueType, comparison);
}
private static FilterNode TranslateRegex(MethodCallExpression m)
{
// Regex.IsMatch(input, pattern)
var input = m.Arguments[0];
var pattern = m.Arguments[1];
if (!TryGetField(input, out var field, out var valueType))
throw new NotSupportedException("Regex.IsMatch input must be a supported field.");
var value = ExtractConstantAsString(pattern);
return new CompareNode(CompareOp.Regex, field, value, valueType);
}
private static bool TryGetField(Expression expr, out FieldRef field, out string? valueType)
{
// RequestAbstract direct fields
if (expr is MemberExpression me && me.Expression?.Type == typeof(RequestAbstract))
{
if (me.Member.Name == "Path")
{
field = new FieldRef("path", "path");
valueType = "string";
return true;
}
if (me.Member.Name == "Method")
{
field = new FieldRef("method", "method");
valueType = "string";
return true;
}
}
// Dictionary indexer: req.Headers["k"]
if (IsDictIndexer(expr, out var dictName, out var key))
{
field = new FieldRef("dict", dictName, key);
valueType = "string";
return true;
}
// JsonPath proxy indexer: req.JsonPath["$.status"]
if (IsJsonPathIndexer(expr, out var path, out var type))
{
field = new FieldRef("body", "jsonPath", path);
valueType = type;
return true;
}
// PathTemplate: req.PathTemplate["/orders/{id:int}"] is boolean
if (IsPathTemplateIndexer(expr, out var template))
{
field = new FieldRef("pathTemplate", "pathTemplate", template);
valueType = "bool";
return true;
}
field = null!;
valueType = null;
return false;
}
private static bool IsStringMethod(MethodCallExpression m, out CompareOp op, out StringComparison comparison)
{
op = default;
comparison = StringComparison.Ordinal;
if (m.Method.DeclaringType != typeof(string))
return false;
if (m.Method.Name == "Contains") { op = CompareOp.Contains; return true; }
if (m.Method.Name == "StartsWith") { op = CompareOp.StartsWith; return true; }
if (m.Method.Name == "EndsWith") { op = CompareOp.EndsWith; return true; }
if (m.Method.Name == "Equals")
{
op = CompareOp.Eq;
if (m.Arguments.Count == 2 && m.Arguments[1] is ConstantExpression ce && ce.Value is StringComparison sc)
comparison = sc;
return true;
}
return false;
}
private static bool IsRegexIsMatch(MethodCallExpression m)
=> m.Method.DeclaringType == typeof(Regex) && m.Method.Name == "IsMatch";
private static bool IsContainsKey(MethodCallExpression m, out string dictName, out string key)
{
if (m.Method.Name == "ContainsKey" && m.Object is MemberExpression dict
&& dict.Member.Name is "Headers" or "QueryParameters" or "PathParameters")
{
dictName = dict.Member.Name;
key = ExtractConstantAsString(m.Arguments[0]);
return true;
}
dictName = key = "";
return false;
}
private static bool IsDictIndexer(Expression expr, out string dictName, out string key)
{
if (expr is MethodCallExpression call && call.Method.Name == "get_Item"
&& call.Object is MemberExpression dict
&& dict.Member.Name is "Headers" or "QueryParameters" or "PathParameters")
{
dictName = dict.Member.Name;
key = ExtractConstantAsString(call.Arguments[0]);
return true;
}
dictName = key = "";
return false;
}
private static bool IsJsonPathIndexer(Expression expr, out string path, out string valueType)
{
// req.JsonPath["$.x"] or req.JsonPath.Int["$.x"]
if (expr is MethodCallExpression call && call.Method.Name == "get_Item")
{
if (call.Object is MemberExpression me && me.Member.Name == "JsonPath")
{
path = ExtractConstantAsString(call.Arguments[0]);
valueType = "string";
return true;
}
if (call.Object is MemberExpression me2 && me2.Member.Name is "Int" or "Decimal" or "Bool"
&& me2.Expression is MemberExpression parent && parent.Member.Name == "JsonPath")
{
path = ExtractConstantAsString(call.Arguments[0]);
valueType = me2.Member.Name switch
{
"Int" => "int",
"Decimal" => "decimal",
"Bool" => "bool",
_ => "string"
};
return true;
}
}
path = valueType = "";
return false;
}
private static bool IsPathTemplateIndexer(Expression expr, out string template)
{
if (expr is MethodCallExpression call && call.Method.Name == "get_Item"
&& call.Object is MemberExpression me && me.Member.Name == "PathTemplate")
{
template = ExtractConstantAsString(call.Arguments[0]);
return true;
}
template = "";
return false;
}
private static string ExtractConstantAsString(Expression expr)
{
if (expr is ConstantExpression c && c.Value is string s)
return s;
var lambda = Expression.Lambda(expr);
var value = lambda.Compile().DynamicInvoke();
return value?.ToString() ?? "";
}
}
// ------------------ Evaluator ------------------
public sealed class V2Evaluator
{
public Func<RequestAbstract, bool> Compile(FilterNode node)
=> req => Evaluate(node, req);
private bool Evaluate(FilterNode node, RequestAbstract req)
=> node switch
{
AndNode a => Evaluate(a.Left, req) && Evaluate(a.Right, req),
OrNode o => Evaluate(o.Left, req) || Evaluate(o.Right, req),
NotNode n => !Evaluate(n.Inner, req),
PathTemplateNode p => MatchPathTemplate(req.Path, p.Template, p.CaseSensitive),
CompareNode c => MatchCompare(c, req),
_ => false
};
private bool MatchCompare(CompareNode c, RequestAbstract req)
{
var actual = ResolveField(c.Field, req);
if (c.Field.Kind == "pathTemplate")
return actual == "true";
return c.Op switch
{
CompareOp.Eq => string.Equals(actual, c.Value, c.Comparison ?? StringComparison.Ordinal),
CompareOp.Neq => !string.Equals(actual, c.Value, c.Comparison ?? StringComparison.Ordinal),
CompareOp.Contains => (actual ?? "").Contains(c.Value ?? "", c.Comparison ?? StringComparison.Ordinal),
CompareOp.StartsWith => (actual ?? "").StartsWith(c.Value ?? "", c.Comparison ?? StringComparison.Ordinal),
CompareOp.EndsWith => (actual ?? "").EndsWith(c.Value ?? "", c.Comparison ?? StringComparison.Ordinal),
CompareOp.Regex => Regex.IsMatch(actual ?? "", c.Value ?? ""),
CompareOp.Exists => !string.IsNullOrEmpty(actual),
CompareOp.Gt or CompareOp.Gte or CompareOp.Lt or CompareOp.Lte => CompareTyped(actual, c),
_ => false
};
}
private string? ResolveField(FieldRef field, RequestAbstract req)
=> field.Kind switch
{
"path" => req.Path,
"method" => req.Method,
"dict" => GetValue(req, field.Name, field.SubKey),
"body" => JsonPathSelect(req, field.SubKey, field.Name),
"pathTemplate" => MatchPathTemplate(req.Path, field.SubKey ?? "", false) ? "true" : "false",
_ => null
};
private static string? GetValue(RequestAbstract req, string dictName, string? key)
{
if (key == null) return null;
var dict = dictName switch
{
"Headers" => req.Headers,
"QueryParameters" => req.QueryParameters,
"PathParameters" => req.PathParameters,
_ => null
};
if (dict == null) return null;
return dict.TryGetValue(key, out var value) ? value : null;
}
private static string? JsonPathSelect(RequestAbstract req, string? path, string name)
{
// Replace with real JSONPath implementation and cached body parsing
_ = name;
_ = req;
return path == null ? null : "";
}
private static bool CompareTyped(string? actual, CompareNode c)
{
if (actual == null) return false;
if (c.ValueType == "int" && int.TryParse(actual, out var a) && int.TryParse(c.Value, out var b))
return c.Op switch
{
CompareOp.Gt => a > b,
CompareOp.Gte => a >= b,
CompareOp.Lt => a < b,
CompareOp.Lte => a <= b,
_ => false
};
if (c.ValueType == "decimal" && decimal.TryParse(actual, out var da) && decimal.TryParse(c.Value, out var db))
return c.Op switch
{
CompareOp.Gt => da > db,
CompareOp.Gte => da >= db,
CompareOp.Lt => da < db,
CompareOp.Lte => da <= db,
_ => false
};
if (c.ValueType == "bool" && bool.TryParse(actual, out var ba) && bool.TryParse(c.Value, out var bb))
return c.Op switch
{
CompareOp.Eq => ba == bb,
CompareOp.Neq => ba != bb,
_ => false
};
return false;
}
private static bool MatchPathTemplate(string path, string template, bool caseSensitive)
{
// Minimal stub; replace with a robust path template engine.
var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
if (template.Contains("{") && template.Contains("}"))
return path.Split('/').Length == template.Split('/').Length;
return string.Equals(path, template, comparison);
}
}
// ------------------ Example ------------------
public static class Example
{
public static void Run()
{
Expression<Func<RequestAbstract, bool>> expr =
req =>
req.PathTemplate["/orders/{id:int}"] &&
req.Method == "POST" &&
req.Headers["Authorization"] == "Bearer ABC123" &&
req.JsonPath["$.status"] == "approved" &&
req.JsonPath.Int["$.amount"] > 100;
var ast = V2ExpressionTranslator.Translate(expr);
var predicate = new V2Evaluator().Compile(ast);
var req = new RequestAbstract
{
Path = "/orders/123",
Method = "POST",
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["Authorization"] = "Bearer ABC123"
}
};
var result = predicate(req);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment