-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Contract dynamic swagger endpoint (#527)
- Loading branch information
Showing
15 changed files
with
1,024 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
src/Stratis.Bitcoin.Features.SmartContracts.Tests/ContractSchemaFactoryTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Microsoft.OpenApi.Models; | ||
using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor; | ||
using Stratis.SmartContracts; | ||
using Stratis.SmartContracts.CLR.Compilation; | ||
using Stratis.SmartContracts.CLR.Loader; | ||
using Xunit; | ||
|
||
namespace Stratis.Bitcoin.Features.SmartContracts.Tests | ||
{ | ||
public class ContractSchemaFactoryTests | ||
{ | ||
private const string Code = @" | ||
using Stratis.SmartContracts; | ||
[Deploy] | ||
public class PrimitiveParams : SmartContract | ||
{ | ||
public PrimitiveParams(ISmartContractState state): base(state) {} | ||
public void AcceptsBool(bool b) {} | ||
public void AcceptsByte(byte bb) {} | ||
public void AcceptsByteArray(byte[] ba) {} | ||
public void AcceptsChar(char c) {} | ||
public void AcceptsString(string s) {} | ||
public void AcceptsUint(uint ui) {} | ||
public void AcceptsUlong(ulong ul) {} | ||
public void AcceptsInt(int i) {} | ||
public void AcceptsLong(long l) {} | ||
public void AcceptsAddress(Address a) {} | ||
public bool SomeProperty {get; set;} | ||
public void AcceptsAllParams(bool b, byte bb, byte[] ba, char c, string s, uint ui, ulong ul, int i, long l, Address a) {} | ||
} | ||
public class DontDeploy : SmartContract | ||
{ | ||
public DontDeploy(ISmartContractState state): base(state) {} | ||
public void SomeMethod(string i) {} | ||
} | ||
"; | ||
[Fact] | ||
public void Map_Parameter_Type_Success() | ||
{ | ||
var compilationResult = ContractCompiler.Compile(Code); | ||
|
||
var assembly = Assembly.Load(compilationResult.Compilation); | ||
|
||
var mapper = new ContractSchemaFactory(); | ||
|
||
MethodInfo methodInfo = assembly.ExportedTypes.First(t => t.Name == "PrimitiveParams").GetMethod("AcceptsAllParams"); | ||
var schema = mapper.Map(methodInfo); | ||
var properties = schema.Properties; | ||
|
||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(bool)]().Type, properties["b"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(byte)]().Type, properties["bb"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(byte[])]().Type, properties["ba"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(char)]().Type, properties["c"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(string)]().Type, properties["s"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(uint)]().Type, properties["ui"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(ulong)]().Type, properties["ul"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(int)]().Type, properties["i"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(long)]().Type, properties["l"].Type); | ||
Assert.Equal(ContractSchemaFactory.PrimitiveTypeMap[typeof(string)]().Type, properties["a"].Type); | ||
} | ||
|
||
[Fact] | ||
public void Map_Type_Success() | ||
{ | ||
var compilationResult = ContractCompiler.Compile(Code); | ||
|
||
var assembly = Assembly.Load(compilationResult.Compilation); | ||
|
||
var mapper = new ContractSchemaFactory(); | ||
|
||
// Maps the methods in a type to schemas. | ||
IDictionary<string, OpenApiSchema> mapped = mapper.Map(new ContractAssembly(assembly).GetPublicMethods()); | ||
|
||
Assert.Equal("AcceptsBool", mapped["AcceptsBool"].Title); | ||
Assert.Equal("AcceptsByte", mapped["AcceptsByte"].Title); | ||
Assert.Equal("AcceptsByteArray", mapped["AcceptsByteArray"].Title); | ||
Assert.Equal("AcceptsChar", mapped["AcceptsChar"].Title); | ||
Assert.Equal("AcceptsString", mapped["AcceptsString"].Title); | ||
Assert.Equal("AcceptsUint", mapped["AcceptsUint"].Title); | ||
Assert.Equal("AcceptsUlong", mapped["AcceptsUlong"].Title); | ||
Assert.Equal("AcceptsInt", mapped["AcceptsInt"].Title); | ||
Assert.Equal("AcceptsLong", mapped["AcceptsLong"].Title); | ||
Assert.Equal("AcceptsAddress", mapped["AcceptsAddress"].Title); | ||
|
||
Assert.Equal(11, mapped.Count); | ||
} | ||
|
||
[Fact] | ||
public void Only_Map_Deployed_Type_Success() | ||
{ | ||
var compilationResult = ContractCompiler.Compile(Code); | ||
|
||
var assembly = Assembly.Load(compilationResult.Compilation); | ||
|
||
var mapper = new ContractSchemaFactory(); | ||
|
||
IDictionary<string, OpenApiSchema> mapped = mapper.Map(new ContractAssembly(assembly)); | ||
|
||
Assert.Equal(11, mapped.Count); | ||
Assert.False(mapped.ContainsKey("SomeMethod")); | ||
} | ||
|
||
[Fact] | ||
public void Only_Map_Deployed_Type_Single_Contract_Success() | ||
{ | ||
string code = @" | ||
using Stratis.SmartContracts; | ||
public class PrimitiveParams : SmartContract | ||
{ | ||
public PrimitiveParams(ISmartContractState state): base(state) {} | ||
public void SomeMethod(string i) {} | ||
} | ||
"; | ||
var compilationResult = ContractCompiler.Compile(code); | ||
|
||
var assembly = Assembly.Load(compilationResult.Compilation); | ||
|
||
var contractAssembly = new ContractAssembly(assembly); | ||
|
||
var mapper = new ContractSchemaFactory(); | ||
|
||
IDictionary<string, OpenApiSchema> mapped = mapper.Map(contractAssembly); | ||
|
||
Assert.Equal(1, mapped.Count); | ||
Assert.True(mapped.ContainsKey("SomeMethod")); | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
src/Stratis.Bitcoin.Features.SmartContracts.Tests/ParameterInfoMapperExtensionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using System.Linq; | ||
using System.Reflection; | ||
using Newtonsoft.Json.Linq; | ||
using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor; | ||
using Stratis.SmartContracts.CLR.Compilation; | ||
using Stratis.SmartContracts.CLR.Serialization; | ||
using Xunit; | ||
|
||
namespace Stratis.Bitcoin.Features.SmartContracts.Tests | ||
{ | ||
public class ParameterInfoMapperExtensionsTests | ||
{ | ||
[Fact] | ||
public void Map_Method_Params_Success() | ||
{ | ||
var code = @" | ||
using Stratis.SmartContracts; | ||
public class Test | ||
{ | ||
public void AcceptsAllParams(bool b, byte bb, byte[] ba, char c, string s, uint ui, ulong ul, int i, long l, Address a) {} | ||
} | ||
"; | ||
var compiled = ContractCompiler.Compile(code).Compilation; | ||
var assembly = Assembly.Load(compiled); | ||
var method = assembly.ExportedTypes.First().GetMethod("AcceptsAllParams"); | ||
|
||
// The jObject as we expect it to come from swagger. | ||
var jObject = JObject.FromObject(new | ||
{ | ||
b = "true", | ||
bb = "DD", | ||
ba = "AABB", | ||
c = 'a', | ||
s = "Test", | ||
ui = 12, | ||
ul = 123123128823, | ||
i = 257, | ||
l = 1238457438573495346, | ||
a = "address" | ||
}); | ||
|
||
var mapped = method.GetParameters().Map(jObject); | ||
|
||
// Check the order and type of each param is correct. | ||
Assert.Equal(10, mapped.Length); | ||
Assert.Equal($"{(int)MethodParameterDataType.Bool}#true", mapped[0]); | ||
Assert.Equal($"{(int)MethodParameterDataType.Byte}#DD", mapped[1]); | ||
Assert.Equal($"{(int)MethodParameterDataType.ByteArray}#AABB", mapped[2]); | ||
Assert.Equal($"{(int)MethodParameterDataType.Char}#a", mapped[3]); | ||
Assert.Equal($"{(int)MethodParameterDataType.String}#Test", mapped[4]); | ||
Assert.Equal($"{(int)MethodParameterDataType.UInt}#12", mapped[5]); | ||
Assert.Equal($"{(int)MethodParameterDataType.ULong}#123123128823", mapped[6]); | ||
Assert.Equal($"{(int)MethodParameterDataType.Int}#257", mapped[7]); | ||
Assert.Equal($"{(int)MethodParameterDataType.Long}#1238457438573495346", mapped[8]); | ||
Assert.Equal($"{(int)MethodParameterDataType.Address}#address", mapped[9]); | ||
|
||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/ContractAssemblyExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Stratis.SmartContracts.CLR.Loader; | ||
|
||
namespace Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor | ||
{ | ||
public static class ContractAssemblyExtensions | ||
{ | ||
/// <summary> | ||
/// Gets the public methods defined by the contract, ignoring property getters/setters. | ||
/// </summary> | ||
/// <returns></returns> | ||
public static IEnumerable<MethodInfo> GetPublicMethods(this IContractAssembly contractAssembly) | ||
{ | ||
Type deployedType = contractAssembly.DeployedType; | ||
|
||
if (deployedType == null) | ||
return new List<MethodInfo>(); | ||
|
||
return deployedType | ||
.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) // Get only the methods declared on the contract type | ||
.Where(m => !m.IsSpecialName); // Ignore property setters/getters | ||
} | ||
|
||
public static IEnumerable<PropertyInfo> GetPublicGetterProperties(this IContractAssembly contractAssembly) | ||
{ | ||
Type deployedType = contractAssembly.DeployedType; | ||
|
||
if (deployedType == null) | ||
return new List<PropertyInfo>(); | ||
|
||
return deployedType | ||
.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) | ||
.Where(p => p.GetGetMethod() != null); | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/ContractSchemaFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Microsoft.OpenApi.Models; | ||
using Stratis.SmartContracts; | ||
using Stratis.SmartContracts.CLR.Loader; | ||
|
||
namespace Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor | ||
{ | ||
/// <summary> | ||
/// Factory for generating swagger schema for smart contract primitives. | ||
/// </summary> | ||
public class ContractSchemaFactory | ||
{ | ||
public static readonly Dictionary<Type, Func<OpenApiSchema>> PrimitiveTypeMap = new Dictionary<Type, Func<OpenApiSchema>> | ||
{ | ||
{ typeof(short), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(ushort), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(int), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(uint), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(long), () => new OpenApiSchema { Type = "integer", Format = "int64" } }, | ||
{ typeof(ulong), () => new OpenApiSchema { Type = "integer", Format = "int64" } }, | ||
{ typeof(float), () => new OpenApiSchema { Type = "number", Format = "float" } }, | ||
{ typeof(double), () => new OpenApiSchema { Type = "number", Format = "double" } }, | ||
{ typeof(decimal), () => new OpenApiSchema { Type = "number", Format = "double" } }, | ||
{ typeof(byte), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(sbyte), () => new OpenApiSchema { Type = "integer", Format = "int32" } }, | ||
{ typeof(byte[]), () => new OpenApiSchema { Type = "string", Format = "byte" } }, | ||
{ typeof(sbyte[]), () => new OpenApiSchema { Type = "string", Format = "byte" } }, | ||
{ typeof(char), () => new OpenApiSchema { Type = "string", Format = "char" } }, | ||
{ typeof(string), () => new OpenApiSchema { Type = "string" } }, | ||
{ typeof(bool), () => new OpenApiSchema { Type = "boolean" } }, | ||
{ typeof(Address), () => new OpenApiSchema { Type = "string" } } | ||
}; | ||
|
||
/// <summary> | ||
/// Maps a contract assembly to its schemas. | ||
/// </summary> | ||
/// <param name="assembly"></param> | ||
/// <returns></returns> | ||
public IDictionary<string, OpenApiSchema> Map(IContractAssembly assembly) | ||
{ | ||
return this.Map(assembly.GetPublicMethods()); | ||
} | ||
|
||
/// <summary> | ||
/// Maps a type to its schemas. | ||
/// </summary> | ||
/// <param name="type"></param> | ||
/// <returns></returns> | ||
public IDictionary<string, OpenApiSchema> Map(IEnumerable<MethodInfo> methods) | ||
{ | ||
return methods.Select(this.Map).ToDictionary(k => k.Title, v => v); | ||
} | ||
|
||
/// <summary> | ||
/// Maps a single method to a schema. | ||
/// </summary> | ||
/// <param name="method"></param> | ||
/// <returns></returns> | ||
public OpenApiSchema Map(MethodInfo method) | ||
{ | ||
var schema = new OpenApiSchema(); | ||
schema.Properties = new Dictionary<string, OpenApiSchema>(); | ||
schema.Title = method.Name; | ||
|
||
foreach (var parameter in method.GetParameters()) | ||
{ | ||
// Default to string. | ||
OpenApiSchema paramSchema = PrimitiveTypeMap.ContainsKey(parameter.ParameterType) | ||
? PrimitiveTypeMap[parameter.ParameterType]() | ||
: PrimitiveTypeMap[typeof(string)](); | ||
|
||
schema.Properties.Add(parameter.Name, paramSchema); | ||
} | ||
|
||
return schema; | ||
} | ||
} | ||
} |
Oops, something went wrong.