diff --git a/src/Nest/Mapping/AttributeBased/ElasticsearchPropertyAttributeBase.cs b/src/Nest/Mapping/AttributeBased/ElasticsearchPropertyAttributeBase.cs index ab0468ab2c7..45d0ea880ac 100644 --- a/src/Nest/Mapping/AttributeBased/ElasticsearchPropertyAttributeBase.cs +++ b/src/Nest/Mapping/AttributeBased/ElasticsearchPropertyAttributeBase.cs @@ -23,6 +23,8 @@ public abstract class ElasticsearchPropertyAttributeBase : Attribute, IProperty, IDictionary IProperty.LocalMetadata { get; set; } + IDictionary IProperty.Meta { get; set; } + PropertyName IProperty.Name { get; set; } private IProperty Self => this; string IProperty.Type { get; set; } diff --git a/src/Nest/Mapping/Types/PropertyBase.cs b/src/Nest/Mapping/Types/PropertyBase.cs index 59d9ffeefec..832e2d4b106 100644 --- a/src/Nest/Mapping/Types/PropertyBase.cs +++ b/src/Nest/Mapping/Types/PropertyBase.cs @@ -20,6 +20,18 @@ public interface IProperty : IFieldMapping [IgnoreDataMember] IDictionary LocalMetadata { get; set; } + /// + /// Metadata attached to the field. This metadata is stored in but opaque to Elasticsearch. It is + /// only useful for multiple applications that work on the same indices to share + /// meta information about fields such as units. + /// + /// Field metadata enforces at most 5 entries, that keys have a length that + /// is less than or equal to 20, and that values are strings whose length is less + /// than or equal to 50. + /// + [DataMember(Name = "meta")] + IDictionary Meta { get; set; } + /// /// The name of the property /// @@ -53,11 +65,14 @@ public abstract class PropertyBase : IProperty, IPropertyWithClrOrigin /// public IDictionary LocalMetadata { get; set; } + /// + public IDictionary Meta { get; set; } + /// public PropertyName Name { get; set; } protected string DebugDisplay => $"Type: {((IProperty)this).Type ?? ""}, Name: {Name.DebugDisplay} "; - + public override string ToString() => DebugDisplay; /// diff --git a/src/Nest/Mapping/Types/PropertyDescriptorBase.cs b/src/Nest/Mapping/Types/PropertyDescriptorBase.cs index 763096d004f..51f7e84b7f7 100644 --- a/src/Nest/Mapping/Types/PropertyDescriptorBase.cs +++ b/src/Nest/Mapping/Types/PropertyDescriptorBase.cs @@ -25,6 +25,7 @@ protected string TypeOverride IDictionary IProperty.LocalMetadata { get; set; } PropertyName IProperty.Name { get; set; } + IDictionary IProperty.Meta { get; set; } string IProperty.Type { @@ -41,5 +42,9 @@ string IProperty.Type /// public TDescriptor LocalMetadata(Func, FluentDictionary> selector) => Assign(selector, (a, v) => a.LocalMetadata = v?.Invoke(new FluentDictionary())); + + /// + public TDescriptor Meta(Func, FluentDictionary> selector) => + Assign(selector, (a, v) => a.Meta = v?.Invoke(new FluentDictionary())); } } diff --git a/src/Nest/Mapping/Types/PropertyFormatter.cs b/src/Nest/Mapping/Types/PropertyFormatter.cs index 1ac573bf4dc..65090d61eef 100644 --- a/src/Nest/Mapping/Types/PropertyFormatter.cs +++ b/src/Nest/Mapping/Types/PropertyFormatter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Elasticsearch.Net.Utf8Json; using Elasticsearch.Net.Utf8Json.Internal; using Elasticsearch.Net.Utf8Json.Resolvers; diff --git a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs index 7759ff7340b..789551ee709 100644 --- a/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs +++ b/src/Nest/Search/FieldCapabilities/FieldCapabilitiesResponse.cs @@ -88,5 +88,8 @@ public class FieldCapabilities [DataMember(Name = "searchable")] public bool Searchable { get; internal set; } + + [DataMember(Name = "meta")] + public IReadOnlyDictionary Meta { get; internal set; } = EmptyReadOnly.Dictionary; } } diff --git a/tests/Tests/ClientConcepts/HighLevel/Serialization/ExtendingNestTypes.doc.cs b/tests/Tests/ClientConcepts/HighLevel/Serialization/ExtendingNestTypes.doc.cs index 584562f29e9..48b0f9bf43e 100644 --- a/tests/Tests/ClientConcepts/HighLevel/Serialization/ExtendingNestTypes.doc.cs +++ b/tests/Tests/ClientConcepts/HighLevel/Serialization/ExtendingNestTypes.doc.cs @@ -35,6 +35,7 @@ public class ExtendingNestTypes public class MyPluginProperty : IProperty { IDictionary IProperty.LocalMetadata { get; set; } + IDictionary IProperty.Meta { get; set; } public string Type { get; set; } = "my_plugin_property"; public PropertyName Name { get; set; } diff --git a/tests/Tests/Mapping/LocalMetadata/LocalMetadataVisitorTests.cs b/tests/Tests/Mapping/LocalMetadata/LocalMetadataVisitorTests.cs index e3d7b9ca5bd..b4525a08767 100644 --- a/tests/Tests/Mapping/LocalMetadata/LocalMetadataVisitorTests.cs +++ b/tests/Tests/Mapping/LocalMetadata/LocalMetadataVisitorTests.cs @@ -4,7 +4,8 @@ using Nest; using Tests.Mapping.Types.Core.Text; -namespace Tests.Mapping.LocalMetadata { +namespace Tests.Mapping.LocalMetadata +{ public class LocalMetadataVisitorTests { [U] @@ -17,7 +18,7 @@ public void CanAssignAndAccessLocalMetadataInitializer() .AddTestLocalMetadata() )) as ITypeMapping; - var visitor = new LocalMatadataVisitor(); + var visitor = new LocalMetadataVisitor(); var walker = new MappingWalker(visitor); walker.Accept(descriptor.Properties); @@ -36,7 +37,7 @@ public void CanAssignAndAccessLocalMetadataFluent() ) )) as ITypeMapping; - var visitor = new LocalMatadataVisitor(); + var visitor = new LocalMetadataVisitor(); var walker = new MappingWalker(visitor); walker.Accept(descriptor.Properties); @@ -63,7 +64,7 @@ public static TDescriptor AddTestLocalMetadata(this TDescriptor des } } - public class LocalMatadataVisitor : NoopMappingVisitor + public class LocalMetadataVisitor : NoopMappingVisitor { public int MetadataCount { get; set; } @@ -77,4 +78,4 @@ public override void Visit(ITextProperty property) property.LocalMetadata.Should().Contain("Test", "TestValue"); } } -} \ No newline at end of file +} diff --git a/tests/Tests/Mapping/Meta/MetaMappingApiTests.cs b/tests/Tests/Mapping/Meta/MetaMappingApiTests.cs new file mode 100644 index 00000000000..0c1082257c9 --- /dev/null +++ b/tests/Tests/Mapping/Meta/MetaMappingApiTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Elastic.Xunit.XunitPlumbing; +using FluentAssertions; +using Nest; +using Tests.ClientConcepts.HighLevel.CovariantHits; +using Tests.Core.Extensions; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; +using Tests.Mapping.Types; + +namespace Tests.Mapping.Meta +{ + [SkipVersion("<7.6.0", "Meta added in Elasticsearch 7.6.0")] + public class MetaMappingApiTests + : PropertyTestsBase + { + public MetaMappingApiTests(WritableCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override Func, IPromise> FluentProperties => p => p + .Number(n => n + .Name(nn => nn.Rank) + .Type(NumberType.Integer) + .Meta(m => m + .Add("unit", "popularity") + ) + ); + protected override IProperties InitializerProperties => new Properties + { + { n => n.Rank, new NumberProperty(NumberType.Integer) + { + Meta = new Dictionary + { + { "unit", "popularity" } + } + } + } + }; + + protected override void ExpectResponse(PutMappingResponse response) + { + base.ExpectResponse(response); + + // check the meta shows up in get mapping API + var getMappingResponse = Client.Indices.GetMapping(m => m.Index(CallIsolatedValue)); + getMappingResponse.IsValid.Should().BeTrue(); + var mappingMeta = getMappingResponse.Indices[CallIsolatedValue].Mappings.Properties["rank"].Meta; + mappingMeta.Should().NotBeNull().And.ContainKey("unit"); + mappingMeta["unit"].Should().Be("popularity"); + + // check the meta shows up in field capabilities API + var fieldCapsResponse = Client.FieldCapabilities(CallIsolatedValue, f => f + .Fields(ff => ff.Rank) + ); + fieldCapsResponse.IsValid.Should().BeTrue(); + var meta = fieldCapsResponse.Fields["rank"].Integer.Meta; + meta.Should().NotBeNull().And.ContainKey("unit"); + meta["unit"].Should().BeEquivalentTo("popularity"); + } + } +} diff --git a/tests/Tests/Mapping/Metafields/MetafieldsMappingApiTestsBase.cs b/tests/Tests/Mapping/Metafields/MetafieldsMappingApiTestsBase.cs deleted file mode 100644 index af4061bd556..00000000000 --- a/tests/Tests/Mapping/Metafields/MetafieldsMappingApiTestsBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Elasticsearch.Net; -using Nest; -using Tests.Core.ManagedElasticsearch.Clusters; -using Tests.Domain; -using Tests.Framework.EndpointTests; -using Tests.Framework.EndpointTests.TestState; - -namespace Tests.Mapping.Metafields -{ - public abstract class MetafieldsMappingApiTestsBase - : ApiTestBase, PutMappingRequest> - { - protected MetafieldsMappingApiTestsBase(ReadOnlyCluster cluster, EndpointUsage usage) : base(cluster, usage) { } - - protected override HttpMethod HttpMethod => HttpMethod.PUT; - protected override string UrlPath => $"/{CallIsolatedValue}/project/_mapping"; - - protected override LazyResponses ClientUsage() => Calls( - (client, f) => client.Map(f), - (client, f) => client.MapAsync(f), - (client, r) => client.Map(r), - (client, r) => client.MapAsync(r) - ); - } -}