Skip to content

Commit 11509c2

Browse files
Adds expression extensions for data manipulation
Introduces `Map` and `Reduce` expressions for transforming collections within expression trees, enabling advanced data manipulation. Adds extension methods to `FetchExpression` to simplify reading response content as JSON, text, bytes, or stream, enhancing web request processing capabilities.
1 parent 5f75b64 commit 11509c2

9 files changed

Lines changed: 728 additions & 33 deletions

File tree

src/Hyperbee.Expressions.Lab/FetchExpression.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ protected override Expression VisitChildren( ExpressionVisitor visitor )
167167

168168
public static partial class ExpressionExtensions
169169
{
170+
public static FetchExpression Fetch(
171+
Expression clientName,
172+
Expression url )
173+
{
174+
return new FetchExpression( url, Expression.Constant( HttpMethod.Get ), null, null, clientName );
175+
}
176+
170177
public static FetchExpression Fetch( Expression clientName,
171178
Expression url,
172179
Expression method,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System.Net.Http.Json;
2+
using System.Linq.Expressions;
3+
using static System.Linq.Expressions.Expression;
4+
using static Hyperbee.Expressions.ExpressionExtensions;
5+
6+
namespace Hyperbee.Expressions.Lab;
7+
8+
public static partial class ExpressionExtensions
9+
{
10+
public static Expression ReadJson( FetchExpression fetch, Type type )
11+
{
12+
return ReadJson( Await( fetch ), type );
13+
}
14+
15+
public static Expression ReadJson( Expression response, Type type )
16+
{
17+
ArgumentNullException.ThrowIfNull( response, nameof(response) );
18+
ArgumentNullException.ThrowIfNull( type, nameof(type) );
19+
20+
if ( response.Type != typeof(HttpResponseMessage) )
21+
throw new ArgumentException( "Response must be of type HttpResponseMessage.", nameof(response) );
22+
23+
var readFromJsonMethodInfo = typeof(HttpContentJsonExtensions)
24+
.GetMethod( nameof(HttpContentJsonExtensions.ReadFromJsonAsync),
25+
[typeof(HttpContent), typeof(CancellationToken)] )!
26+
.MakeGenericMethod( type );
27+
28+
var content = Property( response, nameof(HttpResponseMessage.Content) );
29+
return Call(
30+
null,
31+
readFromJsonMethodInfo,
32+
content,
33+
Default( typeof(CancellationToken) )
34+
);
35+
}
36+
37+
public static Expression ReadText( FetchExpression fetch )
38+
{
39+
return ReadText( Await( fetch ) );
40+
}
41+
42+
public static Expression ReadText( Expression response )
43+
{
44+
ArgumentNullException.ThrowIfNull( response, nameof(response) );
45+
46+
return Call(
47+
Property( response, nameof(HttpResponseMessage.Content) ),
48+
nameof(HttpContent.ReadAsStringAsync),
49+
Type.EmptyTypes
50+
);
51+
}
52+
53+
public static Expression ReadBytes( FetchExpression fetch )
54+
{
55+
return ReadBytes( Await( fetch ) );
56+
}
57+
58+
public static Expression ReadBytes( Expression response )
59+
{
60+
ArgumentNullException.ThrowIfNull( response, nameof(response) );
61+
62+
return Call(
63+
Property( response, nameof(HttpResponseMessage.Content) ),
64+
nameof(HttpContent.ReadAsByteArrayAsync),
65+
Type.EmptyTypes
66+
);
67+
}
68+
69+
public static Expression ReadStream( FetchExpression fetch )
70+
{
71+
return ReadStream( Await( fetch ) );
72+
}
73+
74+
public static Expression ReadStream( Expression response )
75+
{
76+
ArgumentNullException.ThrowIfNull( response, nameof(response) );
77+
78+
return Call(
79+
Property( response, nameof(HttpResponseMessage.Content) ),
80+
nameof(HttpContent.ReadAsStreamAsync),
81+
Type.EmptyTypes
82+
);
83+
}
84+
}

src/Hyperbee.Expressions.Lab/Hyperbee.Expressions.Lab.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1717
<Copyright>Stillpoint Software, Inc.</Copyright>
1818
<Title>Hyperbee Expressions (lab)</Title>
19-
<Description>Extentions for .NET Expression Trees.</Description>
19+
<Description>Sample Extentions for .NET Expression Trees.</Description>
2020
<RepositoryUrl>https://github.com/Stillpoint-Software/Hyperbee.Expressions</RepositoryUrl>
2121
<RepositoryType>git</RepositoryType>
2222
<PackageReleaseNotes>https://github.com/Stillpoint-Software/Hyperbee.Expressions/releases/latest</PackageReleaseNotes>

src/Hyperbee.Expressions.Lab/JsonExpression.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Linq.Expressions;
2-
using System.Reflection;
32
using System.Text.Json;
43

54
using static Hyperbee.Expressions.ExpressionExtensions;
@@ -55,41 +54,12 @@ public override Expression Reduce()
5554
return Await( Call(
5655
deserializeAsyncMethodInfo,
5756
InputExpression,
58-
optionExpression
59-
) );
60-
}
61-
62-
if ( InputExpression.Type == typeof( HttpContent ) )
63-
{
64-
var readStreamMethodInfo = typeof( HttpContent )
65-
.GetMethod( nameof( HttpContent.ReadAsStreamAsync ), Type.EmptyTypes )!;
66-
67-
// Deserialize from HttpContent using the stream
68-
var readStreamAsync = Await(
69-
Call(
70-
InputExpression,
71-
readStreamMethodInfo
72-
)
73-
);
74-
75-
var deserializeAsyncMethodInfo = typeof( JsonSerializer )
76-
.GetMethod( nameof( JsonSerializer.DeserializeAsync ), [
77-
typeof(Stream),
78-
typeof(JsonSerializerOptions),
79-
typeof(CancellationToken)
80-
] )!
81-
.MakeGenericMethod( TargetType );
82-
83-
return Await( Call(
84-
deserializeAsyncMethodInfo,
85-
readStreamAsync,
8657
optionExpression,
8758
Default( typeof( CancellationToken ) )
8859
) );
8960
}
9061

9162
throw new InvalidOperationException( "Unsupported input type for JSON deserialization." );
92-
9363
}
9464

9565
protected override Expression VisitChildren( ExpressionVisitor visitor )
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System.Linq.Expressions;
2+
using static Hyperbee.Expressions.ExpressionExtensions;
3+
4+
namespace Hyperbee.Expressions.Lab;
5+
6+
public delegate Expression MapBody( ParameterExpression item );
7+
public delegate Expression MapBodyIndex( ParameterExpression item, ParameterExpression index );
8+
public delegate Expression MapBodyIndexSource( ParameterExpression item, ParameterExpression index, Expression source );
9+
10+
public class MapExpression : Expression
11+
{
12+
public Expression Collection { get; }
13+
public Expression Body { get; }
14+
15+
public ParameterExpression Item { get; }
16+
public ParameterExpression Index { get; }
17+
public ParameterExpression Source { get; }
18+
public ParameterExpression ResultList { get; }
19+
20+
public Type ResultType { get; }
21+
22+
private MapExpression( Expression collection, Type resultType )
23+
{
24+
ArgumentNullException.ThrowIfNull( collection );
25+
ArgumentNullException.ThrowIfNull( resultType );
26+
27+
if ( !typeof( System.Collections.IEnumerable ).IsAssignableFrom( collection.Type ) )
28+
throw new ArgumentException( "Collection must be IEnumerable", nameof( collection ) );
29+
30+
Collection = collection;
31+
ResultType = resultType;
32+
33+
var enumerableType = collection.Type.GetInterfaces()
34+
.Concat( [collection.Type] )
35+
.FirstOrDefault( t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof( IEnumerable<> ) );
36+
37+
var itemType = enumerableType?.GetGenericArguments()[0] ?? typeof( object );
38+
39+
Item = Variable( itemType, "item" );
40+
ResultList = Variable( typeof( List<> ).MakeGenericType( resultType ), "result" );
41+
}
42+
43+
public MapExpression( Expression collection, Type resultType, MapBody body ) : this( collection, resultType )
44+
{
45+
Body = body( Item );
46+
}
47+
48+
public MapExpression( Expression collection, Type resultType, MapBodyIndex body ) : this( collection, resultType )
49+
{
50+
Index = Variable( typeof( int ), "index" );
51+
Body = body( Item, Index );
52+
}
53+
54+
public MapExpression( Expression collection, Type resultType, MapBodyIndexSource body ) : this( collection, resultType )
55+
{
56+
Index = Variable( typeof( int ), "index" );
57+
Source = Variable( collection.Type, "source" );
58+
Body = body( Item, Index, Collection );
59+
}
60+
61+
public MapExpression( Expression collection, Type resultType, Expression body ) : this( collection, resultType )
62+
{
63+
ArgumentNullException.ThrowIfNull( body );
64+
Body = body;
65+
}
66+
67+
public override ExpressionType NodeType => ExpressionType.Extension;
68+
public override Type Type => typeof( List<> ).MakeGenericType( ResultType );
69+
public override bool CanReduce => true;
70+
71+
public override Expression Reduce()
72+
{
73+
var addMethod = typeof( List<> )
74+
.MakeGenericType( ResultType )
75+
.GetMethod( "Add" )!;
76+
77+
if ( Source != null && Index != null )
78+
{
79+
return Block(
80+
[Item, ResultList, Index, Source],
81+
Assign( ResultList, New( ResultList.Type ) ),
82+
Assign( Source, Collection ),
83+
Assign( Index, Constant( 0 ) ),
84+
ForEach( Source, Item,
85+
Block(
86+
Call( ResultList, addMethod, Body ),
87+
PostIncrementAssign( Index )
88+
)
89+
),
90+
ResultList
91+
);
92+
}
93+
94+
if ( Index != null )
95+
{
96+
return Block(
97+
[Item, ResultList, Index],
98+
Assign( ResultList, New( ResultList.Type ) ),
99+
Assign( Index, Constant( 0 ) ),
100+
ForEach( Collection, Item,
101+
Block(
102+
Call( ResultList, addMethod, Body ),
103+
PostIncrementAssign( Index )
104+
)
105+
),
106+
ResultList
107+
);
108+
}
109+
110+
return Block(
111+
[Item, ResultList],
112+
Assign( ResultList, New( ResultList.Type ) ),
113+
ForEach( Collection, Item,
114+
Call( ResultList, addMethod, Body )
115+
),
116+
ResultList
117+
);
118+
}
119+
120+
protected override Expression VisitChildren( ExpressionVisitor visitor )
121+
{
122+
var newCollection = visitor.Visit( Collection );
123+
var newBody = visitor.Visit( Body );
124+
125+
if ( newCollection == Collection && newBody == Body )
126+
return this;
127+
128+
return new MapExpression( newCollection, ResultType, newBody );
129+
}
130+
}
131+
public static partial class ExpressionExtensions
132+
{
133+
public static MapExpression Map( Expression collection, Type resultType, MapBody body )
134+
=> new( collection, resultType, body );
135+
136+
public static MapExpression Map( Expression collection, MapBody body )
137+
=> new( collection, GetGenericType( collection.Type ), body );
138+
139+
public static MapExpression Map( Expression collection, Type resultType, MapBodyIndex body )
140+
=> new( collection, resultType, body );
141+
142+
public static MapExpression Map( Expression collection, MapBodyIndex body )
143+
=> new( collection, GetGenericType( collection.Type ), body );
144+
145+
public static MapExpression Map( Expression collection, Type resultType, MapBodyIndexSource body )
146+
=> new( collection, resultType, body );
147+
148+
public static MapExpression Map( Expression collection, MapBodyIndexSource body )
149+
=> new( collection, GetGenericType( collection.Type ), body );
150+
151+
private static Type GetGenericType( Type type )
152+
{
153+
var enumerableType = type == typeof(IEnumerable<>)
154+
? type
155+
: type.GetInterfaces()
156+
.FirstOrDefault( t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>) );
157+
158+
var enumerableGenericType = enumerableType?.GetGenericArguments()[0] ?? typeof( object );
159+
160+
if( enumerableGenericType == null )
161+
throw new ArgumentException( "Collection must be IEnumerable", nameof( type ) );
162+
163+
return enumerableGenericType;
164+
}
165+
166+
}

0 commit comments

Comments
 (0)