diff --git a/Tools/Substrate.DotNet.Template/templates/Substrate/.substrate/substrate-config.json b/Tools/Substrate.DotNet.Template/templates/Substrate/.substrate/substrate-config.json
index b230e07..ffccf1d 100644
--- a/Tools/Substrate.DotNet.Template/templates/Substrate/.substrate/substrate-config.json
+++ b/Tools/Substrate.DotNet.Template/templates/Substrate/.substrate/substrate-config.json
@@ -4,9 +4,10 @@
"rest_service": "Substrate.RestService",
"rest_client": "Substrate.RestClient"
},
- "metadata": {
- "websocket": "ws://127.0.0.1:9944"
- },
+ "metadata": {
+ "websocket": "ws://127.0.0.1:9944",
+ "refineMetadata": true
+ },
"rest_client_settings": {
"service_assembly": "Substrate.RestService.dll"
}
diff --git a/Tools/Substrate.DotNet/Extensions/ReflectedEndpointExtensions.cs b/Tools/Substrate.DotNet/Extensions/ReflectedEndpointExtensions.cs
index 6f6258d..26217bf 100644
--- a/Tools/Substrate.DotNet/Extensions/ReflectedEndpointExtensions.cs
+++ b/Tools/Substrate.DotNet/Extensions/ReflectedEndpointExtensions.cs
@@ -20,6 +20,30 @@ namespace Substrate.DotNet.Extensions
///
internal static class ReflectedEndpointExtensions
{
+ ///
+ /// Ensure we are importing all model items.
+ /// Not actually required since we use fully qualified items but we want to get rid of that later.
+ ///
+ ///
+ ///
+ public static void ManageNamespace(CodeNamespace currentNamespace, IReflectedEndpointType defaultReturnType)
+ {
+ currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
+ foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
+ {
+ currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
+
+ // Also import namespace from generic sub arguments
+ if (genericArgument.IsGenericType)
+ {
+ foreach (Type genericSubArgument in genericArgument.GenericTypeArguments)
+ {
+ currentNamespace.Imports.Add(new CodeNamespaceImport(genericSubArgument.Namespace));
+ }
+ }
+ }
+ }
+
///
/// Converts a reflected endpoint to an interface method code element.
/// The generated method is being implemented by an actual client.
@@ -73,13 +97,7 @@ internal static CodeTypeMember ToMockupInterfaceMethod(this IReflectedEndpoint e
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
- // Ensure we are importing all model items.
- // Not actually required since we use fully qualified items but we want to get rid of that later.
- currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
- foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
- {
- currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
- }
+ ManageNamespace(currentNamespace, defaultReturnType);
method.Parameters.Add(new CodeParameterDeclarationExpression(defaultReturnType.Type, "value"));
}
@@ -229,13 +247,7 @@ internal static CodeTypeMember ToMockupClientMethod(this IReflectedEndpoint endp
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
- // Ensure we are importing all model items.
- // Not actually required since we use fully qualified items but we want to get rid of that later.
- clientNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
- foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
- {
- clientNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
- }
+ ManageNamespace(clientNamespace, defaultReturnType);
method.Parameters.Add(new CodeParameterDeclarationExpression(defaultReturnType.Type, "value"));
}
@@ -317,14 +329,7 @@ internal static CodeTypeMember ToUnitTestMethod(this IReflectedEndpoint endpoint
IReflectedEndpointType defaultReturnType = endpoint.GetResponse().GetSuccessReturnType();
if (defaultReturnType != null)
{
- // Ensure we are importing all model items.
- // Not actually required since we use fully qualified items but we want to get rid of that later.
- clientNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
- foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
- {
- clientNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
- }
-
+ ManageNamespace(clientNamespace, defaultReturnType);
GenerateMockupValueStatement(currentMembers, method, defaultReturnType.Type);
}
else
diff --git a/Tools/Substrate.DotNet/Extensions/ReflectedEndpointResponseExtensions.cs b/Tools/Substrate.DotNet/Extensions/ReflectedEndpointResponseExtensions.cs
index 92fe06a..ff69ce6 100644
--- a/Tools/Substrate.DotNet/Extensions/ReflectedEndpointResponseExtensions.cs
+++ b/Tools/Substrate.DotNet/Extensions/ReflectedEndpointResponseExtensions.cs
@@ -27,14 +27,7 @@ internal static CodeTypeReference ToInterfaceMethodReturnType(this IReflectedEnd
return new CodeTypeReference(typeof(void));
}
- // Ensure we are importing all model items.
- // Not actually required since we use fully qualified items but we want to get rid of that later.
- currentNamespace.Imports.Add(new CodeNamespaceImport(defaultReturnType.Type.Namespace));
- foreach (Type genericArgument in defaultReturnType.Type.GenericTypeArguments)
- {
- currentNamespace.Imports.Add(new CodeNamespaceImport(genericArgument.Namespace));
- }
-
+ ReflectedEndpointExtensions.ManageNamespace(currentNamespace, defaultReturnType);
return new CodeTypeReference(typeof(Task<>).MakeGenericType(new[] { defaultReturnType.Type }));
}
diff --git a/Tools/Substrate.DotNet/Program.cs b/Tools/Substrate.DotNet/Program.cs
index d29c4d9..0af3cf2 100644
--- a/Tools/Substrate.DotNet/Program.cs
+++ b/Tools/Substrate.DotNet/Program.cs
@@ -10,6 +10,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using Substrate.DotNet.Service.Generators.Base;
namespace Substrate.DotNet
{
@@ -166,11 +167,22 @@ private static async Task UpgradeOrUpdateSubstrateEnvironmentAsync(bool fe
Log.Information("Using Runtime {runtime}", configuration.Metadata.Runtime);
- // Service
+ if (configuration.Metadata.IsMetadataRefined)
+ {
+ Log.Information("MetaData refined option is activated");
+ SolutionGeneratorBase.RefinedUnnecessaryWrapper(metadata);
+ }
+
+ // NetApi
+ Log.Information("Generate {NetApi} classes", configuration.Projects.NetApi);
GenerateNetApiClasses(metadata, configuration);
+
+ // Service
+ Log.Information("Generate {RestService} classes", configuration.Projects.RestService);
GenerateRestServiceClasses(metadata, configuration);
// Client
+ Log.Information("Generate {RestClient} classes", configuration.Projects.RestClient);
GenerateRestClientClasses(configuration);
return true;
diff --git a/Tools/Substrate.DotNet/Service/Generators/Base/SolutionGeneratorBase.cs b/Tools/Substrate.DotNet/Service/Generators/Base/SolutionGeneratorBase.cs
index c70804d..ff5ff6d 100644
--- a/Tools/Substrate.DotNet/Service/Generators/Base/SolutionGeneratorBase.cs
+++ b/Tools/Substrate.DotNet/Service/Generators/Base/SolutionGeneratorBase.cs
@@ -7,6 +7,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Xml.Linq;
+using System.Reflection.Metadata.Ecma335;
namespace Substrate.DotNet.Service.Generators.Base
{
@@ -145,6 +147,9 @@ private static Dictionary GetRuntimeIndex(Dictionary nodeTypes)
{
+ var metadataNaming = new MetadataNaming(nodeTypes);
+ List rewritedName = new();
+
Dictionary _countPaths = new();
for (uint id = 0; id < nodeTypes.Keys.Max(); id++)
{
@@ -196,10 +201,207 @@ protected static void GetGenericStructs(Dictionary nodeTypes)
if (generics.Contains(key))
{
- type.Path[^1] = type.Path[^1] + "T" + (_countPaths.ContainsKey(key) ? _countPaths[key] : 1);
+ string suggestedClassName = metadataNaming.WriteClassName(type);
+ if(suggestedClassName != type.Path[^1] && !rewritedName.Any(x => x == suggestedClassName))
+ {
+ type.Path[^1] = suggestedClassName;
+ } else
+ {
+ type.Path[^1] = type.Path[^1] + "T" + (_countPaths.ContainsKey(key) ? _countPaths[key] : 1);
+ }
+ rewritedName.Add(type.Path[^1]);
}
}
}
}
+
+ ///
+ /// Refine current metadata by removing unecessary classes that encapsulate mostly Rust lists
+ /// C# is more permissive so we don't need to wrap BaseVec<> into a class
+ ///
+ public static void RefinedUnnecessaryWrapper(MetaData metadata)
+ {
+ Dictionary nodeTypes = metadata.NodeMetadata.Types;
+ var metadataNaming = new MetadataNaming(nodeTypes);
+
+ bool hasSomethingChanged = false;
+ do
+ {
+ IDictionary wrapperNodes = ExtractWrappers(nodeTypes);
+
+ // Loop over all node types to switch sourceId to new destinationId
+ hasSomethingChanged = MapSourceToDestination(nodeTypes, wrapperNodes);
+
+ if(hasSomethingChanged)
+ {
+ RemoveSourceIds(nodeTypes, metadataNaming, wrapperNodes);
+ RefineModules(metadata, wrapperNodes);
+ }
+ } while (hasSomethingChanged);
+ }
+
+ ///
+ /// Remove all unecessary id
+ ///
+ ///
+ ///
+ ///
+ private static void RemoveSourceIds(Dictionary nodeTypes, MetadataNaming metadataNaming, IDictionary wrapperNodes)
+ {
+ foreach (KeyValuePair node in wrapperNodes)
+ {
+ Log.Verbose("\t Replace {sourceType} (id = {sourceKey}) by {destinationType} (id = {destinationKey})", metadataNaming.WriteType(node.Key), node.Key, metadataNaming.WriteType(node.Value), node.Value);
+ nodeTypes.Remove(node.Key);
+ }
+ }
+
+ ///
+ /// Loop over all modules and replace old occurences
+ ///
+ ///
+ ///
+ private static void RefineModules(MetaData metadata, IDictionary wrapperNodes)
+ {
+ Dictionary modules = metadata.NodeMetadata.Modules;
+ foreach (KeyValuePair module in modules)
+ {
+ PalletStorage storage = module.Value.Storage;
+
+ if (storage == null || storage.Entries == null)
+ {
+ continue;
+ }
+
+ foreach (Entry entry in storage.Entries)
+ {
+ if (wrapperNodes.ContainsKey(entry.TypeMap.Item1))
+ {
+ entry.TypeMap = new(wrapperNodes[entry.TypeMap.Item1], entry.TypeMap.Item2);
+ }
+ if (entry.TypeMap.Item2 != null && wrapperNodes.ContainsKey(entry.TypeMap.Item2.Key))
+ {
+ entry.TypeMap.Item2.Key = wrapperNodes[entry.TypeMap.Item2.Key];
+ }
+
+ if (entry.TypeMap.Item2 != null && wrapperNodes.ContainsKey(entry.TypeMap.Item2.Value))
+ {
+ entry.TypeMap.Item2.Value = wrapperNodes[entry.TypeMap.Item2.Value];
+ }
+
+ }
+ }
+ }
+
+ ///
+ /// Check every TypeDef composite which have only on TypeDef Sequence as property field.
+ /// Target multi generic references (BoundedVec, WeakBoundedVec etc)
+ /// Return a dictionnary of sourceId, destinationId
+ ///
+ ///
+ ///
+ private static IDictionary ExtractWrappers(Dictionary nodeTypes)
+ {
+ var wrappers =
+ nodeTypes
+ .Where(x => x.Value.TypeDef == TypeDefEnum.Composite)
+ .Select(x => (NodeTypeComposite)x.Value)
+ .Where(x => x.Path != null)
+ .GroupBy(x => string.Join('.', x.Path))
+ .Where(x => x.Count() > 1)
+ .SelectMany(x => x)
+ .Where(x => x.TypeFields != null && x.TypeFields.Length == 1)
+ .Where(x => nodeTypes[x.TypeFields[0].TypeId].TypeDef == TypeDefEnum.Sequence)
+ .Select(x => new
+ {
+ sourceId = x.Id,
+ sourceName = x.Path != null ? string.Join(".", x.Path) : string.Empty,
+ destinationId = nodeTypes[x.TypeFields[0].TypeId].Id
+ });
+
+ IDictionary wrapperNodes = wrappers.ToDictionary(x => x.sourceId, x => x.destinationId);
+ return wrapperNodes;
+ }
+
+ ///
+ /// Change all occurences of old Id to Destination Id
+ ///
+ ///
+ ///
+ /// True if a node has been changed
+ private static bool MapSourceToDestination(Dictionary nodeTypes, IDictionary wrapperNodes)
+ {
+ bool anyUpdate = false;
+ foreach (KeyValuePair node in nodeTypes)
+ {
+ switch (node.Value)
+ {
+ case NodeTypeVariant detailVariant when detailVariant.Variants is not null:
+ foreach (NodeTypeField nodeTypeField in detailVariant.Variants
+ .Where(x => x.TypeFields != null)
+ .SelectMany(x => x.TypeFields))
+ {
+ if (wrapperNodes.ContainsKey(nodeTypeField.TypeId))
+ {
+ nodeTypeField.TypeId = wrapperNodes[nodeTypeField.TypeId];
+ anyUpdate = true;
+ }
+ }
+ break;
+
+ case NodeTypeCompact detailCompact when wrapperNodes.ContainsKey(detailCompact.TypeId):
+ detailCompact.TypeId = wrapperNodes[detailCompact.TypeId];
+ anyUpdate = true;
+ break;
+
+ case NodeTypeComposite detailComposite when detailComposite.TypeFields is not null:
+ foreach (NodeTypeField typeField in detailComposite.TypeFields)
+ {
+ if (wrapperNodes.ContainsKey(typeField.TypeId))
+ {
+ typeField.TypeId = wrapperNodes[typeField.TypeId];
+ anyUpdate = true;
+ }
+ }
+ break;
+
+ case NodeTypeSequence detailSequence when wrapperNodes.ContainsKey(detailSequence.TypeId):
+ detailSequence.TypeId = wrapperNodes[detailSequence.TypeId];
+ anyUpdate = true;
+ break;
+
+ case NodeTypeTuple detailTuple when detailTuple.TypeIds != null:
+ for (int i = 0; i < detailTuple.TypeIds.Length; i++)
+ {
+ if (wrapperNodes.ContainsKey(detailTuple.TypeIds[i]))
+ {
+ detailTuple.TypeIds[i] = wrapperNodes[detailTuple.TypeIds[i]];
+ anyUpdate = true;
+ }
+ }
+ break;
+
+ case NodeTypeArray detailArray when wrapperNodes.ContainsKey(detailArray.TypeId):
+ detailArray.TypeId = wrapperNodes[detailArray.TypeId];
+ anyUpdate = true;
+ break;
+
+ case NodeTypeBitSequence detailBitSequence:
+ if(wrapperNodes.ContainsKey(detailBitSequence.TypeIdStore))
+ {
+ detailBitSequence.TypeIdStore = wrapperNodes[detailBitSequence.TypeIdStore];
+ }
+
+ if (wrapperNodes.ContainsKey(detailBitSequence.TypeIdOrder))
+ {
+ detailBitSequence.TypeIdOrder = wrapperNodes[detailBitSequence.TypeIdOrder];
+ }
+
+ string x = "1";
+ break;
+ }
+ }
+
+ return anyUpdate;
+ }
}
}
\ No newline at end of file
diff --git a/Tools/Substrate.DotNet/Service/Generators/MetadataNaming.cs b/Tools/Substrate.DotNet/Service/Generators/MetadataNaming.cs
new file mode 100644
index 0000000..214ac10
--- /dev/null
+++ b/Tools/Substrate.DotNet/Service/Generators/MetadataNaming.cs
@@ -0,0 +1,163 @@
+using Substrate.DotNet.Extensions;
+using Substrate.NetApi.Model.Meta;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Substrate.DotNet.Service.Generators
+{
+ public class MetadataNaming
+ {
+ public const string DefaultTypeSeparator = "";
+ private readonly Dictionary _nodeTypes;
+
+ public bool DisplayPropertyName { get; init; } = true;
+ public bool AggregatePropertyType { get; init; } = false;
+
+ public MetadataNaming(Dictionary nodeTypes)
+ {
+ _nodeTypes = nodeTypes;
+ }
+
+ public string WriteType(uint typeId)
+ => WriteType(GetPalletType(typeId));
+
+ public string WriteType(NodeType detailType)
+ {
+
+ if (detailType is NodeTypeVariant detailVariant)
+ {
+ return WriteNodeVariant(detailVariant);
+ }
+ else if (detailType is NodeTypeCompact detailCompact)
+ {
+ return WriteNodeCompact(detailCompact);
+ }
+ else if (detailType is NodeTypePrimitive detailPrimitive)
+ {
+ return WriteNodePrimitive(detailPrimitive);
+ }
+ else if (detailType is NodeTypeComposite detailComposite)
+ {
+ return WriteNodeComposite(detailComposite);
+ }
+ else if (detailType is NodeTypeSequence detailSequence)
+ {
+ return WriteNodeSequence(detailSequence);
+ }
+ else if (detailType is NodeTypeTuple detailTuple)
+ {
+ return WriteNodeTuple(detailTuple);
+ }
+ else if (detailType is NodeTypeArray detailArray)
+ {
+ return WriteNodeArray(detailArray);
+ }
+ else
+ {
+ throw new NotSupportedException("Type not supported yet..."); // BitSequence ??
+ }
+ }
+
+ public string WriteNodeVariant(NodeTypeVariant nodeType)
+ {
+ return nodeType.Path[^1];
+ }
+
+ public string WriteNodeCompact(NodeTypeCompact nodeType)
+ {
+ return $"{nodeType.TypeDef}{WriteType(nodeType.TypeId)}";
+ }
+
+ public string WriteNodePrimitive(NodeTypePrimitive nodeType)
+ {
+ return nodeType.Primitive.ToString().ToUpperFirst();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// Define if we have to display class details
+ ///
+ public string WriteNodeComposite(NodeTypeComposite nodeType, bool expandDetails = false)
+ {
+ string display = nodeType.Path[^1];
+ if (expandDetails && nodeType.TypeParams != null && nodeType.TypeParams.Length > 0)
+ {
+ string fullName = string.Join(
+ DefaultTypeSeparator,
+ nodeType.TypeFields.Select(x => $"{(DisplayPropertyName ? x.Name?.ToUpperFirst() : string.Empty)}{WriteType(x.TypeId)}"));
+
+ display += $"{DefaultTypeSeparator}{fullName}";
+ }
+ return display;
+ }
+
+ public string WriteNodeSequence(NodeTypeSequence nodeType)
+ {
+ return $"Vec{WriteType(nodeType.TypeId)}";
+ }
+
+ public string WriteNodeTuple(NodeTypeTuple nodeType)
+ {
+ return $"Tuple{string.Join(DefaultTypeSeparator, nodeType.TypeIds.Select(WriteType))}";
+ }
+
+ public string WriteNodeArray(NodeTypeArray nodeType)
+ {
+ return $"{nodeType.Length}{WriteType(nodeType.TypeId)}";
+ }
+
+ public NodeType GetPalletType(uint typeId)
+ {
+ NodeType nodeType = default;
+ _nodeTypes.TryGetValue(typeId, out nodeType);
+
+ if (nodeType == null)
+ {
+ throw new KeyNotFoundException($"{nameof(nodeType)} is not found in current metadata type");
+ }
+
+ return nodeType;
+ }
+
+ public string WriteClassName(NodeTypeComposite nodeType)
+ {
+ string display = nodeType.Path[^1];
+ if (nodeType.TypeParams != null && nodeType.TypeParams.Length > 0)
+ {
+ var propType = nodeType.TypeFields.Select(x => WriteType(x.TypeId)).ToList();
+
+ string suffix = propType.Count switch
+ {
+ 1 => $"{propType[0]}",
+ 2 when propType[0] == propType[1] => $"{propType[0]}",
+ > 2 => ((Func)(() => {
+ if (propType.All(x => propType[0] == x)) {
+ return $"{propType[0]}s";
+ }
+
+ IEnumerable> groupedName = propType.GroupBy(x => x);
+ if (groupedName.Count() < propType.Count)
+ {
+ return string.Join("_", groupedName.Where(x => x.Count() > 1).Select(x => x.Key));
+ }
+
+ // No good solution found
+ return string.Empty;
+ }))()
+ };
+
+ if (!string.IsNullOrEmpty(suffix))
+ {
+ return $"{display}_{suffix}";
+ }
+ }
+ return display;
+ }
+ }
+}
diff --git a/Tools/Substrate.DotNet/SubstrateConfiguration.cs b/Tools/Substrate.DotNet/SubstrateConfiguration.cs
index 2dbd42f..42556f8 100644
--- a/Tools/Substrate.DotNet/SubstrateConfiguration.cs
+++ b/Tools/Substrate.DotNet/SubstrateConfiguration.cs
@@ -51,6 +51,9 @@ public class SubstrateConfigurationMetadata
[JsonIgnore]
public string Runtime { get; set; }
+
+ [JsonProperty("refineMetadata")]
+ public bool IsMetadataRefined { get; set; }
}
public class SubstrateConfigurationRestClientSettings