From 55504e4ee1965168df2431391328edf779c41f2f Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 1 Oct 2018 14:06:51 +0200 Subject: [PATCH 1/2] Processors can now tested in isolation and guarded in isolation with a SkipVersion --- .../Tests.Configuration/tests.default.yaml | 2 +- .../SerializationTesterAssertionExtensions.cs | 11 +- .../DeletePipeline/DeletePipelineApiTests.cs | 3 +- .../Ingest/GetPipeline/GetPipelineApiTests.cs | 1 + .../GrokProcessorPatternsApiTests.cs | 0 .../GrokProcessorPatternsTests.cs | 0 .../GrokProcessorPatternsUnitTests.cs | 0 src/Tests/Tests/Ingest/ProcessorAssertions.cs | 345 +++++++++++++++++ .../Ingest/PutPipeline/PutPipelineApiTests.cs | 348 +----------------- 9 files changed, 365 insertions(+), 345 deletions(-) rename src/Tests/Tests/Ingest/{Processor => GrokProcessorPatterns}/GrokProcessorPatternsApiTests.cs (100%) rename src/Tests/Tests/Ingest/{Processor => GrokProcessorPatterns}/GrokProcessorPatternsTests.cs (100%) rename src/Tests/Tests/Ingest/{Processor => GrokProcessorPatterns}/GrokProcessorPatternsUnitTests.cs (100%) create mode 100644 src/Tests/Tests/Ingest/ProcessorAssertions.cs diff --git a/src/Tests/Tests.Configuration/tests.default.yaml b/src/Tests/Tests.Configuration/tests.default.yaml index af781e2fe08..d5070e54ecd 100644 --- a/src/Tests/Tests.Configuration/tests.default.yaml +++ b/src/Tests/Tests.Configuration/tests.default.yaml @@ -5,7 +5,7 @@ # tracked by git). # mode either u (unit test), i (integration test) or m (mixed mode) -mode: u +mode: m # the elasticsearch version that should be started # Can be a snapshot version of sonatype or "latest" to get the latest snapshot of sonatype elasticsearch_version: 6.4.1 diff --git a/src/Tests/Tests.Core/Extensions/SerializationTesterAssertionExtensions.cs b/src/Tests/Tests.Core/Extensions/SerializationTesterAssertionExtensions.cs index 541be627f20..1c2af9eef38 100644 --- a/src/Tests/Tests.Core/Extensions/SerializationTesterAssertionExtensions.cs +++ b/src/Tests/Tests.Core/Extensions/SerializationTesterAssertionExtensions.cs @@ -1,12 +1,17 @@ -using FluentAssertions; +using System; using Tests.Core.Serialization; namespace Tests.Core.Extensions { public static class SerializationTesterAssertionExtensions { - public static void ShouldBeValid(this SerializationResult result, string message = null) => - result.Success.Should().BeTrue("{0}", message + result); + public static void ShouldBeValid(this SerializationResult result, string message = null) + { + if (result.Success) return; + throw new Exception($@"Expected serialization to succeed but failed. +{(message ?? string.Empty) + result} +"); + } public static T AssertRoundTrip(this SerializationTester tester, T @object, string message = null, bool preserveNullInExpected = false) { diff --git a/src/Tests/Tests/Ingest/DeletePipeline/DeletePipelineApiTests.cs b/src/Tests/Tests/Ingest/DeletePipeline/DeletePipelineApiTests.cs index 568878bdb02..45fb17d903a 100644 --- a/src/Tests/Tests/Ingest/DeletePipeline/DeletePipelineApiTests.cs +++ b/src/Tests/Tests/Ingest/DeletePipeline/DeletePipelineApiTests.cs @@ -4,11 +4,10 @@ using Tests.Core.ManagedElasticsearch.Clusters; using Tests.Framework; using Tests.Framework.Integration; -using Tests.Framework.ManagedElasticsearch.Clusters; -using Xunit; namespace Tests.Ingest.DeletePipeline { + //integration test is part of PipelineCrudTests public class DeletePipelineApiTests : ApiTestBase { public DeletePipelineApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } diff --git a/src/Tests/Tests/Ingest/GetPipeline/GetPipelineApiTests.cs b/src/Tests/Tests/Ingest/GetPipeline/GetPipelineApiTests.cs index 8e7d02137c7..5661952b237 100644 --- a/src/Tests/Tests/Ingest/GetPipeline/GetPipelineApiTests.cs +++ b/src/Tests/Tests/Ingest/GetPipeline/GetPipelineApiTests.cs @@ -9,6 +9,7 @@ namespace Tests.Ingest.GetPipeline { + //integration test is part of PipelineCrudTests public class GetPipelineApiTests : ApiTestBase { public GetPipelineApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } diff --git a/src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsApiTests.cs b/src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsApiTests.cs similarity index 100% rename from src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsApiTests.cs rename to src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsApiTests.cs diff --git a/src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsTests.cs b/src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsTests.cs similarity index 100% rename from src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsTests.cs rename to src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsTests.cs diff --git a/src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsUnitTests.cs b/src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsUnitTests.cs similarity index 100% rename from src/Tests/Tests/Ingest/Processor/GrokProcessorPatternsUnitTests.cs rename to src/Tests/Tests/Ingest/GrokProcessorPatterns/GrokProcessorPatternsUnitTests.cs diff --git a/src/Tests/Tests/Ingest/ProcessorAssertions.cs b/src/Tests/Tests/Ingest/ProcessorAssertions.cs new file mode 100644 index 00000000000..52e1d6c32c3 --- /dev/null +++ b/src/Tests/Tests/Ingest/ProcessorAssertions.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Elastic.Xunit.XunitPlumbing; +using Nest; +using Tests.Core.Client; +using Tests.Domain; + +namespace Tests.Ingest +{ + using ProcFunc = Func>>; + public interface IProcessorAssertion + { + object Json { get; } + string Key { get; } + IProcessor Initializer { get; } + ProcFunc Fluent { get; } + } + public abstract class ProcessorAssertion : IProcessorAssertion + { + public abstract string Key { get; } + public abstract IProcessor Initializer { get; } + public abstract ProcFunc Fluent { get; } + public abstract object Json { get; } + } + + + public static class ProcessorAssertions + { + public static IEnumerable All => + from t in typeof(ProcessorAssertions).GetNestedTypes() + where typeof(IProcessorAssertion).IsAssignableFrom(t) && t.IsClass + let a = t.GetCustomAttributes(typeof(SkipVersionAttribute)).FirstOrDefault() as SkipVersionAttribute + where a == null || !a.Ranges.Any(r => r.IsSatisfied(TestClient.Configuration.ElasticsearchVersion)) + select (IProcessorAssertion) (Activator.CreateInstance(t)); + + public static Dictionary[] Json => + All.Select(a => new Dictionary + { + {a.Key, a.Json} + }).ToArray(); + + public static IPromise> Fluent(ProcessorsDescriptor d) + { + foreach (var a in All) a.Fluent(d); + return d; + } + + public static IProcessor[] Initializer => All.Select(a => a.Initializer).ToArray(); + + public class Append : ProcessorAssertion + { + public override string Key => "append"; + public override object Json => new {field = "state", value = new[] {"Stable", "VeryActive"}}; + + public override IProcessor Initializer => new AppendProcessor + { + Field = "state", + Value = new object[] {StateOfBeing.Stable, StateOfBeing.VeryActive} + }; + public override Func>> Fluent => d => d + .Append(a => a + .Field(p => p.State) + .Value(StateOfBeing.Stable, StateOfBeing.VeryActive) + ); + + } + + public class Convert : ProcessorAssertion + { + public override string Key => "convert"; + public override object Json => new + { + field = "numberOfCommits", + target_field = "targetField", + type = "string" + }; + + public override IProcessor Initializer => new ConvertProcessor + { + Field = "numberOfCommits", + TargetField = "targetField", + Type = ConvertProcessorType.String + }; + public override Func>> Fluent => d => d + .Convert(c => c + .Field(p => p.NumberOfCommits) + .TargetField("targetField") + .Type(ConvertProcessorType.String) + ); + } + + public class Date : ProcessorAssertion + { + public override string Key => "date"; + public override object Json => new + { + field = "startedOn", + target_field = "timestamp", + formats = new[] {"dd/MM/yyyy hh:mm:ss"}, + timezone = "Europe/Amsterdam" + }; + + public override IProcessor Initializer => new DateProcessor + { + Field = "startedOn", + TargetField = "timestamp", + Formats = new string[] {"dd/MM/yyyy hh:mm:ss"}, + Timezone = "Europe/Amsterdam" + }; + public override Func>> Fluent => d => d + .Date(dt => dt + .Field(p => p.StartedOn) + .TargetField("timestamp") + .Formats("dd/MM/yyyy hh:mm:ss") + .Timezone("Europe/Amsterdam") + ); + } + + public class Fail : ProcessorAssertion + { + public override string Key => "fail"; + + public override object Json => new { message = "an error message" }; + + public override IProcessor Initializer => new FailProcessor {Message = "an error message"}; + public override Func>> Fluent => d => d.Fail(f => f.Message("an error message")); + } + + public class Foreach : ProcessorAssertion + { + public override string Key => "foreach"; + + public override object Json => new + { + field = "tags", + processor = new + { + uppercase = new + { + field = "_value.name" + } + } + }; + + public override IProcessor Initializer => new ForeachProcessor + { + Field = Infer.Field(p => p.Tags), + Processor = new UppercaseProcessor + { + Field = "_value.name" + } + }; + public override Func>> Fluent => d => d + .Foreach(fe => fe + .Field(p => p.Tags) + .Processor(pps => pps + .Uppercase(uc => uc + .Field("_value.name") + ) + ) + ); + } + + public class Grok : ProcessorAssertion + { + public override string Key => "grok"; + + public override object Json => new + { + field = "description", + patterns = new[] {"my %{FAVORITE_DOG:dog} is colored %{RGB:color}"}, + pattern_definitions = new Dictionary + { + {"FAVORITE_DOG", "border collie"}, + {"RGB", "RED|BLUE|GREEN"}, + } + }; + + public override IProcessor Initializer => new GrokProcessor + { + Field = "description", + Patterns = new[] {"my %{FAVORITE_DOG:dog} is colored %{RGB:color}"}, + PatternDefinitions = new Dictionary + { + {"FAVORITE_DOG", "border collie"}, + {"RGB", "RED|BLUE|GREEN"}, + } + }; + public override Func>> Fluent => d => d + .Grok(gk => gk + .Field(p => p.Description) + .Patterns("my %{FAVORITE_DOG:dog} is colored %{RGB:color}") + .PatternDefinitions(pds => pds + .Add("FAVORITE_DOG", "border collie") + .Add("RGB", "RED|BLUE|GREEN") + ) + ); + } + + public class Gsub : ProcessorAssertion + { + public override string Key => "gsub"; + + public override object Json => new { field = "name", pattern = "-", replacement = "_" }; + + public override IProcessor Initializer => new GsubProcessor + { + Field = "name", + Pattern = "-", + Replacement = "_" + }; + public override Func>> Fluent => d => d + .Gsub(gs => gs + .Field(p => p.Name) + .Pattern("-") + .Replacement("_") + ); + } + + public class Join : ProcessorAssertion + { + public override string Key => "join"; + + public override object Json => new { field = "branches", separator = "," }; + + public override IProcessor Initializer => new JoinProcessor + { + Field = "branches", + Separator = "," + }; + public override Func>> Fluent => d => d.Join(j => j.Field(p => p.Branches).Separator(",")); + } + + public class Lowercase : ProcessorAssertion + { + public override string Key => "lowercase"; + + public override object Json => new { field = "name" }; + + public override IProcessor Initializer => new LowercaseProcessor { Field = "name" }; + public override Func>> Fluent => d => d.Lowercase(l => l.Field(p => p.Name)); + } + + public class Remove : ProcessorAssertion + { + public override string Key => "remove"; + + public override object Json => new { field = "suggest" }; + + public override IProcessor Initializer => new RemoveProcessor {Field = "suggest"}; + public override Func>> Fluent => d => d.Remove(r => r.Field(p => p.Suggest)); + } + + public class Rename : ProcessorAssertion + { + public override string Key => "rename"; + + public override object Json => new { field = "leadDeveloper", target_field = "projectLead" }; + + public override IProcessor Initializer => new RenameProcessor + { + Field = "leadDeveloper", + TargetField = "projectLead" + }; + public override Func>> Fluent => d => d.Rename(rn => rn.Field(p => p.LeadDeveloper).TargetField("projectLead")); + } + + public class Set : ProcessorAssertion + { + public override string Key => "set"; + + public override object Json => new { field = "name", value = "foo" }; + + public override IProcessor Initializer => new SetProcessor { Field = Infer.Field(p => p.Name), Value = "foo" }; + public override Func>> Fluent => d => d.Set(s => s.Field(p => p.Name).Value("foo")); + } + + public class Split : ProcessorAssertion + { + public override string Key => "split"; + + public override object Json => new { field = "description", separator = "." }; + + public override IProcessor Initializer => new SplitProcessor {Field = "description", Separator = "."}; + public override Func>> Fluent => d => d.Split(sp => sp.Field(p => p.Description).Separator(".")); + } + + public class Trim : ProcessorAssertion + { + public override string Key => "trim"; + + public override object Json => new { field = "name" }; + + public override IProcessor Initializer => new TrimProcessor {Field = "name"}; + public override Func>> Fluent => d => d.Trim(t => t.Field(p => p.Name)); + } + + public class Uppercase : ProcessorAssertion + { + public override string Key => "uppercase"; + + public override object Json => new { field = "name" }; + + public override IProcessor Initializer => new UppercaseProcessor {Field = "name"}; + public override Func>> Fluent => d => d.Uppercase(u => u.Field(p => p.Name)); + } + + public class DotExpander : ProcessorAssertion + { + public override string Key => "dot_expander"; + + public override object Json => new { field = "field.withDots" }; + + public override IProcessor Initializer => new DotExpanderProcessor {Field = "field.withDots"}; + public override Func>> Fluent => d => d.DotExpander(de => de.Field("field.withDots")); + } + + public class Script : ProcessorAssertion + { + public override string Key => "script"; + + public override object Json => new { source = "ctx.numberOfCommits++" }; + + public override IProcessor Initializer => new ScriptProcessor {Source = "ctx.numberOfCommits++"}; + public override Func>> Fluent => d => d.Script(s => s.Source("ctx.numberOfCommits++")); + } + + [SkipVersion("<6.1.0", "uses url decode which was introduced in 6.1.0")] + public class UrlDecode : ProcessorAssertion + { + public override string Key => "urldecode"; + + public override object Json => new { field = "description", ignore_missing = true }; + + public override IProcessor Initializer => new UrlDecodeProcessor { Field = "description", IgnoreMissing = true }; + public override Func>> Fluent => d => d + .UrlDecode(ud => ud + .Field(p => p.Description) + .IgnoreMissing() + ); + } + } +} diff --git a/src/Tests/Tests/Ingest/PutPipeline/PutPipelineApiTests.cs b/src/Tests/Tests/Ingest/PutPipeline/PutPipelineApiTests.cs index ea2fa3c8bef..adb3b55c1fe 100644 --- a/src/Tests/Tests/Ingest/PutPipeline/PutPipelineApiTests.cs +++ b/src/Tests/Tests/Ingest/PutPipeline/PutPipelineApiTests.cs @@ -1,21 +1,18 @@ using System; using Elasticsearch.Net; +using FluentAssertions; using Nest; +using Tests.Core.Extensions; using Tests.Framework; using Tests.Framework.Integration; -using Xunit; -using System.Collections.Generic; -using Elastic.Xunit.XunitPlumbing; using Tests.Core.ManagedElasticsearch.Clusters; -using Tests.Domain; namespace Tests.Ingest.PutPipeline { - [SkipVersion("<6.1.0", "uses url decode which was introduced in 6.1.0")] public class PutPipelineApiTests - : ApiIntegrationTestBase + : ApiIntegrationTestBase { - public PutPipelineApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + public PutPipelineApiTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } private static readonly string _id = "pipeline-1"; @@ -35,348 +32,21 @@ protected override LazyResponses ClientUsage() => Calls( protected override object ExpectJson { get; } = new { description = "My test pipeline", - processors = new object[] - { - new - { - append = new - { - field = "state", - value = new [] { "Stable", "VeryActive" } - } - }, - new - { - convert = new - { - field = "numberOfCommits", - target_field = "targetField", - type = "string" - } - }, - new - { - date = new - { - field = "startedOn", - target_field = "timestamp", - formats = new [] { "dd/MM/yyyy hh:mm:ss" }, - timezone = "Europe/Amsterdam" - } - }, - new - { - fail = new - { - message = "an error message" - } - }, - new - { - @foreach = new - { - field = "tags", - processor = new - { - uppercase = new - { - field = "_value.name" - } - } - } - }, - new - { - grok = new - { - field = "description", - patterns = new[] { "my %{FAVORITE_DOG:dog} is colored %{RGB:color}" }, - pattern_definitions = new Dictionary - { - { "FAVORITE_DOG", "border collie" }, - { "RGB", "RED|BLUE|GREEN" }, - } - } - }, - new - { - gsub = new - { - field = "name", - pattern = "-", - replacement = "_" - } - }, - new - { - join = new - { - field = "branches", - separator = "," - } - }, - new - { - lowercase = new - { - field = "name" - } - }, - new - { - remove = new - { - field = "suggest" - } - }, - new - { - rename = new - { - field = "leadDeveloper", - target_field = "projectLead" - } - }, - new - { - set = new - { - field = "name", - value = "foo" - } - }, - new - { - split = new - { - field = "description", - separator = "." - } - }, - new - { - trim = new - { - field = "name" - } - }, - new - { - uppercase = new - { - field = "name" - } - }, - new - { - dot_expander = new - { - field = "field.withDots" - } - }, - new - { - script = new - { - source = "ctx.numberOfCommits++" - } - }, - new - { - urldecode = new - { - field = "description", - ignore_missing = true - } - } - } + processors = ProcessorAssertions.Json }; protected override PutPipelineDescriptor NewDescriptor() => new PutPipelineDescriptor(_id); protected override Func Fluent => d => d .Description("My test pipeline") - .Processors(ps => ps - .Append(a => a - .Field(p => p.State) - .Value(StateOfBeing.Stable, StateOfBeing.VeryActive) - ) - .Convert(c => c - .Field(p => p.NumberOfCommits) - .TargetField("targetField") - .Type(ConvertProcessorType.String) - ) - .Date(dt => dt - .Field(p => p.StartedOn) - .TargetField("timestamp") - .Formats("dd/MM/yyyy hh:mm:ss") - .Timezone("Europe/Amsterdam") - ) - .Fail(f => f - .Message("an error message") - ) - .Foreach(fe => fe - .Field(p => p.Tags) - .Processor(pps => pps - .Uppercase(uc => uc - .Field("_value.name") - ) - ) - ) - .Grok(gk => gk - .Field(p => p.Description) - .Patterns("my %{FAVORITE_DOG:dog} is colored %{RGB:color}") - .PatternDefinitions(pds => pds - .Add("FAVORITE_DOG", "border collie") - .Add("RGB", "RED|BLUE|GREEN") - ) - ) - .Gsub(gs => gs - .Field(p => p.Name) - .Pattern("-") - .Replacement("_") - ) - .Join(j => j - .Field(p => p.Branches) - .Separator(",") - ) - .Lowercase(l => l - .Field(p => p.Name) - ) - .Remove(r => r - .Field(p => p.Suggest) - ) - .Rename(rn => rn - .Field(p => p.LeadDeveloper) - .TargetField("projectLead") - ) - .Set(s => s - .Field(p => p.Name) - .Value("foo") - ) - .Split(sp => sp - .Field(p => p.Description) - .Separator(".") - ) - .Trim(t => t - .Field(p => p.Name) - ) - .Uppercase(u => u - .Field(p => p.Name) - ) - .DotExpander(de => de - .Field("field.withDots") - ) - .Script(s => s - .Source("ctx.numberOfCommits++") - ) - .UrlDecode(ud => ud - .Field(p => p.Description) - .IgnoreMissing() - ) - ); + .Processors(ProcessorAssertions.Fluent); protected override PutPipelineRequest Initializer => new PutPipelineRequest(_id) { Description = "My test pipeline", - Processors = new IProcessor[] - { - new AppendProcessor - { - Field = "state", - Value = new object [] { StateOfBeing.Stable, StateOfBeing.VeryActive } - }, - new ConvertProcessor - { - Field = "numberOfCommits", - TargetField = "targetField", - Type = ConvertProcessorType.String - }, - new DateProcessor - { - Field = "startedOn", - TargetField = "timestamp", - Formats = new string [] { "dd/MM/yyyy hh:mm:ss"}, - Timezone = "Europe/Amsterdam" - }, - new FailProcessor - { - Message = "an error message" - }, - new ForeachProcessor - { - Field = Infer.Field(p => p.Tags), - Processor = new UppercaseProcessor - { - Field = "_value.name" - } - }, - new GrokProcessor - { - Field = "description", - Patterns = new [] { "my %{FAVORITE_DOG:dog} is colored %{RGB:color}" }, - PatternDefinitions = new Dictionary - { - { "FAVORITE_DOG", "border collie" }, - { "RGB", "RED|BLUE|GREEN" }, - } - }, - new GsubProcessor - { - Field = "name", - Pattern = "-", - Replacement = "_" - }, - new JoinProcessor - { - Field = "branches", - Separator = "," - }, - new LowercaseProcessor - { - Field = "name" - }, - new RemoveProcessor - { - Field = "suggest" - }, - new RenameProcessor - { - Field = "leadDeveloper", - TargetField = "projectLead" - }, - new SetProcessor - { - Field = Infer.Field(p => p.Name), - Value = "foo" - }, - new SplitProcessor - { - Field = "description", - Separator = "." - }, - new TrimProcessor - { - Field = "name" - }, - new UppercaseProcessor - { - Field = "name" - }, - new DotExpanderProcessor - { - Field = "field.withDots" - }, - new ScriptProcessor - { - Source = "ctx.numberOfCommits++" - }, - new UrlDecodeProcessor - { - Field = "description", - IgnoreMissing = true - } - } + Processors = ProcessorAssertions.Initializer }; + + protected override void ExpectResponse(IPutPipelineResponse response) => response.Acknowledged.Should().BeTrue(); } } From da0c9026657374adefb0501ba533abb0ec32b306 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 1 Oct 2018 15:05:08 +0200 Subject: [PATCH 2/2] Add ignore_missing to the remove processor --- src/Nest/Ingest/Processors/RemoveProcessor.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Nest/Ingest/Processors/RemoveProcessor.cs b/src/Nest/Ingest/Processors/RemoveProcessor.cs index 1c77bbadb29..bbb35948fa9 100644 --- a/src/Nest/Ingest/Processors/RemoveProcessor.cs +++ b/src/Nest/Ingest/Processors/RemoveProcessor.cs @@ -1,10 +1,6 @@ using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; namespace Nest { @@ -14,14 +10,26 @@ public interface IRemoveProcessor : IProcessor { [JsonProperty("field")] Field Field { get; set; } + + /// + /// If true and does not exist or is null, + /// the processor quietly exits without modifying the document. Default is false + /// + [JsonProperty("ignore_missing")] + bool? IgnoreMissing { get; set; } } + /// public class RemoveProcessor : ProcessorBase, IRemoveProcessor { protected override string Name => "remove"; + /// public Field Field { get; set; } + /// + public bool? IgnoreMissing { get; set; } } + /// public class RemoveProcessorDescriptor : ProcessorDescriptorBase, IRemoveProcessor>, IRemoveProcessor where T : class @@ -29,10 +37,15 @@ public class RemoveProcessorDescriptor protected override string Name => "remove"; Field IRemoveProcessor.Field { get; set; } + bool? IRemoveProcessor.IgnoreMissing { get; set; } + /// public RemoveProcessorDescriptor Field(Field field) => Assign(a => a.Field = field); - public RemoveProcessorDescriptor Field(Expression> objectPath) => - Assign(a => a.Field = objectPath); + /// + public RemoveProcessorDescriptor Field(Expression> objectPath) => Assign(a => a.Field = objectPath); + + /// + public RemoveProcessorDescriptor IgnoreMissing(bool? ignoreMissing = true) => Assign(a => a.IgnoreMissing = ignoreMissing); } }