mirror of https://github.com/Squidex/squidex.git
6 changed files with 398 additions and 15 deletions
@ -0,0 +1,137 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// =========================================-=================================
|
|||
|
|||
using System.Globalization; |
|||
using System.Text; |
|||
using System.Text.RegularExpressions; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.HandleRules |
|||
{ |
|||
public class RuleEventFormatter |
|||
{ |
|||
private const string Undefined = "UNDEFINED"; |
|||
private const string AppIdPlaceholder = "$APP_ID"; |
|||
private const string AppNamePlaceholder = "$APP_NAME"; |
|||
private const string SchemaIdPlaceholder = "$SCHEMA_ID"; |
|||
private const string SchemaNamePlaceholder = "$SCHEMA_NAME"; |
|||
private const string TimestampDatePlaceholder = "$TIMESTAMP_DATE"; |
|||
private const string TimestampDateTimePlaceholder = "$TIMESTAMP_DATETIME"; |
|||
private static readonly Regex ContentDataPlaceholder = new Regex(@"\$CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); |
|||
private readonly JsonSerializer serializer; |
|||
|
|||
public RuleEventFormatter(JsonSerializer serializer) |
|||
{ |
|||
Guard.NotNull(serializer, nameof(serializer)); |
|||
|
|||
this.serializer = serializer; |
|||
} |
|||
|
|||
public virtual JToken ToRouteData(object value) |
|||
{ |
|||
return JToken.FromObject(value, serializer); |
|||
} |
|||
|
|||
public virtual string FormatString(string text, Envelope<AppEvent> @event) |
|||
{ |
|||
var sb = new StringBuilder(text); |
|||
|
|||
if (@event.Headers.Contains(CommonHeaders.Timestamp)) |
|||
{ |
|||
var timestamp = @event.Headers.Timestamp().ToDateTimeUtc(); |
|||
|
|||
sb.Replace(TimestampDateTimePlaceholder, timestamp.ToString("yyy-MM-dd-hh-mm-ss", CultureInfo.InvariantCulture)); |
|||
sb.Replace(TimestampDatePlaceholder, timestamp.ToString("yyy-MM-dd", CultureInfo.InvariantCulture)); |
|||
} |
|||
|
|||
if (@event.Payload.AppId != null) |
|||
{ |
|||
sb.Replace(AppIdPlaceholder, @event.Payload.AppId.Id.ToString()); |
|||
sb.Replace(AppNamePlaceholder, @event.Payload.AppId.Name); |
|||
} |
|||
|
|||
if (@event.Payload is SchemaEvent schemaEvent && schemaEvent.SchemaId != null) |
|||
{ |
|||
sb.Replace(SchemaIdPlaceholder, schemaEvent.SchemaId.Id.ToString()); |
|||
sb.Replace(SchemaNamePlaceholder, schemaEvent.SchemaId.Name); |
|||
} |
|||
|
|||
var result = sb.ToString(); |
|||
|
|||
if (@event.Payload is ContentCreated contentCreated && contentCreated.Data != null) |
|||
{ |
|||
result = ReplaceData(contentCreated.Data, result); |
|||
} |
|||
|
|||
if (@event.Payload is ContentUpdated contentUpdated && contentUpdated.Data != null) |
|||
{ |
|||
result = ReplaceData(contentUpdated.Data, result); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static string ReplaceData(NamedContentData data, string text) |
|||
{ |
|||
return ContentDataPlaceholder.Replace(text, match => |
|||
{ |
|||
var captures = match.Groups[2].Captures; |
|||
|
|||
var path = new string[captures.Count]; |
|||
|
|||
for (var i = 0; i < path.Length; i++) |
|||
{ |
|||
path[i] = captures[i].Value; |
|||
} |
|||
|
|||
if (!data.TryGetValue(path[0], out var field)) |
|||
{ |
|||
return Undefined; |
|||
} |
|||
|
|||
if (!field.TryGetValue(path[1], out var value)) |
|||
{ |
|||
return Undefined; |
|||
} |
|||
|
|||
for (var j = 2; j < path.Length; j++) |
|||
{ |
|||
if (value is JObject obj && obj.TryGetValue(path[j], out value)) |
|||
{ |
|||
continue; |
|||
} |
|||
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count) |
|||
{ |
|||
value = arr[idx]; |
|||
} |
|||
else |
|||
{ |
|||
return Undefined; |
|||
} |
|||
} |
|||
|
|||
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined) |
|||
{ |
|||
return Undefined; |
|||
} |
|||
|
|||
if (value is JValue jValue && jValue != null) |
|||
{ |
|||
return jValue.Value.ToString(); |
|||
} |
|||
|
|||
return value?.ToString(Formatting.Indented) ?? Undefined; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,246 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.HandleRules; |
|||
using Squidex.Domain.Apps.Events; |
|||
using Squidex.Domain.Apps.Events.Contents; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
using Xunit; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Operations.HandleRules |
|||
{ |
|||
public class RuleEventFormatterTests |
|||
{ |
|||
private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); |
|||
private readonly RuleEventFormatter sut; |
|||
|
|||
public RuleEventFormatterTests() |
|||
{ |
|||
sut = new RuleEventFormatter(serializer); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_serialize_object_to_json() |
|||
{ |
|||
var result = sut.ToRouteData(new { Value = 1 }); |
|||
|
|||
Assert.True(result is JObject); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_replace_app_information_from_event() |
|||
{ |
|||
var appId = Guid.NewGuid(); |
|||
|
|||
var @event = new ContentCreated |
|||
{ |
|||
AppId = new NamedId<Guid>(appId, "my-app") |
|||
}; |
|||
|
|||
var result = sut.FormatString("Name $APP_NAME has id $APP_ID", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal($"Name my-app has id {appId}", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_replace_schema_information_from_event() |
|||
{ |
|||
var schemaId = Guid.NewGuid(); |
|||
|
|||
var @event = new ContentCreated |
|||
{ |
|||
SchemaId = new NamedId<Guid>(schemaId, "my-schema") |
|||
}; |
|||
|
|||
var result = sut.FormatString("Name $SCHEMA_NAME has id $SCHEMA_ID", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal($"Name my-schema has id {schemaId}", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_replace_timestamp_information_from_event() |
|||
{ |
|||
var now = DateTime.UtcNow; |
|||
|
|||
var envelope = Envelope.Create(new ContentCreated()).To<AppEvent>().SetTimestamp(Instant.FromDateTimeUtc(now)); |
|||
|
|||
var result = sut.FormatString("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME", envelope); |
|||
|
|||
Assert.Equal($"Date: {now:yyyy-MM-dd}, Full: {now:yyyy-MM-dd-hh-mm-ss}", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_field_not_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", "Berlin")) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.country.iv", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_partition_not_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", "Berlin")) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.de", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_array_item_not_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", new JArray())) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.de.10", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_property_not_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", new JObject( |
|||
new JProperty("name", "Berlin")))) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.de.Name", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_plain_value_when_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", "Berlin")) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("Berlin", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_null() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", JValue.CreateNull())) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_undefined_when_undefined() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", JValue.CreateUndefined())) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("UNDEFINED", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_string_when_object() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", new JObject( |
|||
new JProperty("name", "Berlin")))) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_plain_value_from_array_when_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", new JArray( |
|||
"Berlin"))) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv.0", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("Berlin", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_plain_value_from_object_when_found() |
|||
{ |
|||
var @event = new ContentCreated |
|||
{ |
|||
Data = |
|||
new NamedContentData() |
|||
.AddField("city", new ContentFieldData() |
|||
.AddValue("iv", new JObject( |
|||
new JProperty("name", "Berlin")))) |
|||
}; |
|||
|
|||
var result = sut.FormatString("$CONTENT_DATA.city.iv.name", Envelope.Create(@event).To<AppEvent>()); |
|||
|
|||
Assert.Equal("Berlin", result); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue