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

Implement FluidValue.WriteToAsync #667

Merged
merged 3 commits into from
May 29, 2024
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
7 changes: 7 additions & 0 deletions Fluid.Tests/Domain/WithInterfaces/PetValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace Fluid.Tests.Domain.WithInterfaces
{
Expand Down Expand Up @@ -42,11 +43,17 @@ public override string ToStringValue()
throw new NotImplementedException();
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}

protected override FluidValue GetValue(string name, TemplateContext context)
{
if (name == "Name")
Expand Down
30 changes: 30 additions & 0 deletions Fluid.Tests/MvcViewEngine/NoSyncStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Fluid.Tests.MvcViewEngine
{
/// <summary>
/// Stream implementation that prevents non-async usages.
/// </summary>
public sealed class NoSyncStream : Stream
{
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => Task.CompletedTask;
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default) => ValueTask.CompletedTask;


public override void Flush() => throw new InvalidOperationException();
public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException();
public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException();
public override void SetLength(long value) => throw new InvalidOperationException();
public override void Write(byte[] buffer, int offset, int count) => throw new InvalidOperationException();

public override bool CanRead { get; } = false;
public override bool CanSeek { get; } = false;
public override bool CanWrite { get; } = true;
public override long Length { get; }
public override long Position { get; set; }
}
}
25 changes: 25 additions & 0 deletions Fluid.Tests/MvcViewEngine/ViewEngineTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Fluid.Tests.Mocks;
using Fluid.ViewEngine;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -278,5 +279,29 @@ public async Task LayoutShouldBeAbleToIncludeVarsFromViewStart()

Assert.Equal("[TITLE][SUBTITLE][ViewStart][View]", sw.ToString());
}

[Fact]
public async Task RenderViewOnlyAsyncStream_LargePropertyValue_Nested_SmallBuffer_BiggerThan128LengthString()
{
_mockFileProvider.Add("Views/Index.liquid", "{% layout '_Layout' %}{% section bigboy %}{{BigString}}{% endsection %} ");
_mockFileProvider.Add("Views/_Layout.liquid", "{% rendersection bigboy %}");

await using var sw = new StreamWriter(new NoSyncStream(), bufferSize: 10);
var template = new TemplateContext(new { BigString = new string(Enumerable.Range(0, 129).Select(x => 'b').ToArray()) });
await _renderer.RenderViewAsync(sw, "Index.liquid", template);
await sw.FlushAsync();
}

[Fact]
public async Task RenderViewOnlyAsyncStream_LargePropertyValue_Nested()
{
_mockFileProvider.Add("Views/Index.liquid", "{% layout '_Layout' %}{% section bigboy %}{{BigString}}{% endsection %} ");
_mockFileProvider.Add("Views/_Layout.liquid", "{% rendersection bigboy %}");

await using var sw = new StreamWriter(new NoSyncStream());
var template = new TemplateContext(new { BigString = new string(Enumerable.Range(0, 1500).Select(_ => 'b').ToArray()) });
await _renderer.RenderViewAsync(sw, "Index.liquid", template);
await sw.FlushAsync();
}
}
}
2 changes: 1 addition & 1 deletion Fluid/Ast/CycleStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public override async ValueTask<Completion> WriteToAsync(TextWriter writer, Text
var value = await Values[(int)index].EvaluateAsync(context);
context.SetValue(groupValue, NumberValue.Create(index + 1));

value.WriteTo(writer, encoder, context.CultureInfo);
await value.WriteToAsync(writer, encoder, context.CultureInfo);

return Completion.Normal;
}
Expand Down
6 changes: 3 additions & 3 deletions Fluid/Ast/DecrementStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public DecrementStatement(string identifier)

public string Identifier { get; }

public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context)
{
context.IncrementSteps();

Expand All @@ -35,9 +35,9 @@ public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncode

context.SetValue(prefixedIdentifier, value);

value.WriteTo(writer, encoder, context.CultureInfo);
await value.WriteToAsync(writer, encoder, context.CultureInfo);

return Normal();
return Completion.Normal;
}

protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitDecrementStatement(this);
Expand Down
15 changes: 13 additions & 2 deletions Fluid/Ast/IncrementStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ public override ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncode

context.SetValue(prefixedIdentifier, value);

value.WriteTo(writer, encoder, context.CultureInfo);
var task = value.WriteToAsync(writer, encoder, context.CultureInfo);

return Normal();
if (task.IsCompletedSuccessfully)
{
return new ValueTask<Completion>(Completion.Normal);
}

return Awaited(task);

static async ValueTask<Completion> Awaited(ValueTask t)
{
await t;
return Completion.Normal;
}
}

protected internal override Statement Accept(AstVisitor visitor) => visitor.VisitIncrementStatement(this);
Expand Down
18 changes: 15 additions & 3 deletions Fluid/Ast/OutputStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static async ValueTask<Completion> Awaited(
TemplateContext ctx)
{
var value = await t;
value.WriteTo(w, enc, ctx.CultureInfo);
await value.WriteToAsync(w, enc, ctx.CultureInfo);
return Completion.Normal;
}

Expand All @@ -32,8 +32,20 @@ static async ValueTask<Completion> Awaited(
var task = Expression.EvaluateAsync(context);
if (task.IsCompletedSuccessfully)
{
task.Result.WriteTo(writer, encoder, context.CultureInfo);
return new ValueTask<Completion>(Completion.Normal);
var valueTask = task.Result.WriteToAsync(writer, encoder, context.CultureInfo);

if (valueTask.IsCompletedSuccessfully)
{
return new ValueTask<Completion>(Completion.Normal);
}

return AwaitedWriteTo(valueTask);

static async ValueTask<Completion> AwaitedWriteTo(ValueTask t)
{
await t;
return Completion.Normal;
}
}

return Awaited(task, writer, encoder, context);
Expand Down
11 changes: 11 additions & 0 deletions Fluid/Values/ArrayValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public override decimal ToNumberValue()

public IReadOnlyList<FluidValue> Values { get; }

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
Expand All @@ -109,6 +110,16 @@ public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo
}
}

public override async ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);

foreach (var v in Values)
{
await writer.WriteAsync(v.ToStringValue());
}
}

public override string ToStringValue()
{
return String.Join("", Values.Select(x => x.ToStringValue()));
Expand Down
6 changes: 6 additions & 0 deletions Fluid/Values/BlankValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@ public override bool IsNil()
return true;
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
return default;
}

public override bool Equals(object obj)
{
// The is operator will return false if null
Expand Down
25 changes: 23 additions & 2 deletions Fluid/Values/BooleanValue.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using Fluid.Utils;
using System.Globalization;
using System.Text.Encodings.Web;

namespace Fluid.Values
Expand Down Expand Up @@ -28,7 +29,7 @@ public static BooleanValue Create(bool value)
public override bool Equals(FluidValue other)
{
// blank == false -> true
if (other.Type == FluidValues.Blank) return _value == false;
if (other.Type == FluidValues.Blank) return !_value;

return _value == other.ToBooleanValue();
}
Expand All @@ -48,12 +49,32 @@ public override string ToStringValue()
return _value ? "true" : "false";
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
writer.Write(encoder.Encode(ToStringValue()));
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
var task = writer.WriteAsync(encoder.Encode(ToStringValue()));

if (task.IsCompletedSuccessfully())
{
return default;
}

return Awaited(task);

static async ValueTask Awaited(Task t)
{
await t;
return;
}
}

public override object ToObjectValue()
{
return _value ? BoxedTrue : BoxedFalse;
Expand Down
23 changes: 22 additions & 1 deletion Fluid/Values/DateTimeValue.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Globalization;
using Fluid.Utils;
using System.Globalization;
using System.Text.Encodings.Web;

namespace Fluid.Values
Expand Down Expand Up @@ -44,12 +45,32 @@ public override string ToStringValue()
return _value.ToString("u", CultureInfo.InvariantCulture);
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
writer.Write(_value.ToString("u", cultureInfo));
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
var task = writer.WriteAsync(_value.ToString("u", cultureInfo));

if (task.IsCompletedSuccessfully())
{
return default;
}

return Awaited(task);

static async ValueTask Awaited(Task t)
{
await t;
return;
}
}

public override object ToObjectValue()
{
return _value;
Expand Down
6 changes: 6 additions & 0 deletions Fluid/Values/DictionaryValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,16 @@ public override decimal ToNumberValue()
return 0;
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
return default;
}

public override string ToStringValue()
{
return "";
Expand Down
6 changes: 6 additions & 0 deletions Fluid/Values/EmptyValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,16 @@ public override bool IsNil()
return true;
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
return default;
}

public override bool Equals(object obj)
{
// The is operator will return false if null
Expand Down
20 changes: 20 additions & 0 deletions Fluid/Values/FactoryValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,30 @@ public override string ToStringValue()
return _factory.Value.ToStringValue();
}

[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
_factory.Value.WriteTo(writer, encoder, cultureInfo);
}

public override ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
AssertWriteToParameters(writer, encoder, cultureInfo);
var task = _factory.Value.WriteToAsync(writer, encoder, cultureInfo);

if (task.IsCompletedSuccessfully)
{
return default;
}

return Awaited(task);

static async ValueTask Awaited(ValueTask t)
{
await t;
return;
}
}
}
}
9 changes: 9 additions & 0 deletions Fluid/Values/FluidValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@ namespace Fluid.Values
public abstract class FluidValue : IEquatable<FluidValue>
#pragma warning restore CA1067
{
[Obsolete("WriteTo is obsolete, prefer the WriteToAsync method.")]
public abstract void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo);

public virtual ValueTask WriteToAsync(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{
#pragma warning disable CS0618 // Type or member is obsolete
WriteTo(writer, encoder, cultureInfo);
#pragma warning restore CS0618 // Type or member is obsolete
return default;
}

private static Dictionary<Type, Type> _genericDictionaryTypeCache = new();

[Conditional("DEBUG")]
Expand Down
Loading