Skip to content

Commit

Permalink
Add support for terraform set and tuple types (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamuelFisher committed Jul 15, 2023
1 parent 535e246 commit f1f2695
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 15 deletions.
16 changes: 14 additions & 2 deletions TerraformPluginDotNet.Test/Functional/TerraformResourceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public async Task TestPlanCreateAllFields()
boolean_attribute = true
float_attribute = 1.0
double_attribute = 1.0
string_set_attribute = ["one", "two"]
string_list_attribute = ["one", "two", "three"]
int_list_attribute = [1, 2, 3]
string_map_attribute = {
Expand All @@ -70,6 +71,7 @@ public async Task TestPlanCreateAllFields()
object_2 = {
required_string = "value"
}
tuple_attribute = [1, "value"]
}
""");

Expand Down Expand Up @@ -114,7 +116,15 @@ public async Task TestPlanCreateAllFields()
"a": "one",
"b": "two",
"c": "three"
}
},
"string_set_attribute": [
"one",
"two"
],
"tuple_attribute": [
1,
"value"
]
}
""";

Expand Down Expand Up @@ -166,7 +176,9 @@ public async Task TestPlanCreateOnlyRequiredFields()
"required_int": 1,
"required_string": "value",
"string_list_attribute": null,
"string_map_attribute": null
"string_map_attribute": null,
"string_set_attribute": null,
"tuple_attribute": null
}
""";

Expand Down
2 changes: 1 addition & 1 deletion TerraformPluginDotNet.Test/Schemas/SchemaBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void TestSchema()
Assert.That(schema.Block, Is.Not.Null);

var attributes = schema.Block.Attributes;
Assert.That(attributes, Has.Count.EqualTo(13));
Assert.That(attributes, Has.Count.EqualTo(15));

var idAttr = attributes.Single(x => x.Name == "id");
Assert.That(idAttr.Type.ToStringUtf8(), Is.EqualTo("\"string\""));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using MessagePack;
using NUnit.Framework;
using TerraformPluginDotNet.Resources;
using TerraformPluginDotNet.Schemas.Types;

namespace TerraformPluginDotNet.Test.Schemas.Types;
Expand All @@ -20,14 +22,45 @@ public void Setup()
[TestCase(typeof(bool), ExpectedResult = @"""bool""")]
[TestCase(typeof(float), ExpectedResult = @"""number""")]
[TestCase(typeof(double), ExpectedResult = @"""number""")]
[TestCase(typeof(List<string>), ExpectedResult = @"[""list"",""string""]")]
[TestCase(typeof(List<int>), ExpectedResult = @"[""list"",""number""]")]
[TestCase(typeof(Dictionary<string, string>), ExpectedResult = @"[""map"",""string""]")]
[TestCase(typeof(Dictionary<string, int>), ExpectedResult = @"[""map"",""number""]")]
[TestCase(typeof(Tuple<string>), ExpectedResult = """["tuple",["string"]]""")]
[TestCase(typeof(Tuple<int, int>), ExpectedResult = """["tuple",["number","number"]]""")]
[TestCase(typeof(Tuple<bool, bool, bool>), ExpectedResult = """["tuple",["bool","bool","bool"]]""")]
[TestCase(typeof(Tuple<string, string, string, string>), ExpectedResult = """["tuple",["string","string","string","string"]]""")]
[TestCase(typeof(Tuple<string, string, string, string, string>), ExpectedResult = """["tuple",["string","string","string","string","string"]]""")]
[TestCase(typeof(Tuple<string, string, string, string, string, string>), ExpectedResult = """["tuple",["string","string","string","string","string","string"]]""")]
[TestCase(typeof(Tuple<string, int, bool, string, int, bool, float>), ExpectedResult = """["tuple",["string","number","bool","string","number","bool","number"]]""")]
[TestCase(typeof(Tuple<string, string, string, string, string, string, string, string>), ExpectedResult = """["tuple",["string","string","string","string","string","string","string","string"]]""")]
[TestCase(typeof(List<string>), ExpectedResult = """["list","string"]""")]
[TestCase(typeof(List<int>), ExpectedResult = """["list","number"]""")]
[TestCase(typeof(Dictionary<string, string>), ExpectedResult = """["map","string"]""")]
[TestCase(typeof(Dictionary<string, int>), ExpectedResult = """["map","number"]""")]
[TestCase(typeof(TestObjectWithOptionalAttributes), ExpectedResult = """["object",{"nested_int":"number","required_string":"string"},["nested_int"]]""")]
[TestCase(typeof(TestObjectNoOptionalAttributes), ExpectedResult = """["object",{"required_string":"string"},[]]""")]
public string TestGetTerraformTypeAsJson(Type inputType)
{
return _typeBuilder.GetTerraformType(inputType).ToJson();
}

[Test]
public void TestObjectMissingMessagePackAttribute()
{
Assert.Throws<InvalidOperationException>(() => _typeBuilder.GetTerraformType(typeof(TestMissingAttrObjectResource)));
}

[SchemaVersion(1)]
[MessagePackObject]
class TestMissingAttrObjectResource
{
[Key("object_attribute")]
[Required]
public TestMissingAttrObject ObjectAttribute { get; set; }
}

// Missing [MessagePackObject]
class TestMissingAttrObject
{
[Key("some_attribute")]
[Required]
public string SomeAttribute { get; set; }
}
}
11 changes: 10 additions & 1 deletion TerraformPluginDotNet.Test/TestResource.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using MessagePack;
using TerraformPluginDotNet.Resources;
Expand Down Expand Up @@ -41,6 +42,10 @@ public class TestResource
[Description("A double attribute.")]
public float? DoubleAttribute { get; set; }

[Key("string_set_attribute")]
[Description("A string set attribute.")]
public HashSet<string> StringSetAttribute { get; set; }

[Key("string_list_attribute")]
[Description("A string list attribute.")]
public List<string> StringListAttribute { get; set; }
Expand All @@ -66,6 +71,10 @@ public class TestResource
[Description("An object.")]
[Required]
public TestObjectNoOptionalAttributes Object2 { get; set; }

[Key("tuple_attribute")]
[Description("A tuple attribute.")]
public Tuple<int, string> TupleAttribute { get; set; }
}

[MessagePackObject]
Expand Down
47 changes: 47 additions & 0 deletions TerraformPluginDotNet/Schemas/Types/TerraformType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,53 @@ public sealed record TfList(TerraformType ElementType) : TerraformType()
public override string ToJson() => $"""["list",{ElementType.ToJson()}]""";
}

public sealed record TfSet(TerraformType ElementType) : TerraformType()
{
public override string ToJson() => $"""["set",{ElementType.ToJson()}]""";
}

public sealed record TfTuple : TerraformType
{
public TfTuple(ImmutableList<TerraformType> elementTypes)
{
ElementTypes = elementTypes;
}

public ImmutableList<TerraformType> ElementTypes { get; }

public override string ToJson()
{
var elementTypes = string.Join(",", ElementTypes.Select(x => x.ToJson()));
return $"[\"tuple\",[{elementTypes}]]";
}

public bool Equals(TfTuple? other)
{
if (other == null)
{
return false;
}

return ElementTypes.Count == other.ElementTypes.Count &&
!ElementTypes.Except(other.ElementTypes).Any();
}

public override int GetHashCode()
{
unchecked
{
int hash = 17;

foreach (var element in ElementTypes)
{
hash = hash * 31 + element.GetHashCode();
}

return hash;
}
}
}

public sealed record TfObject : TerraformType
{
public TfObject(
Expand Down
103 changes: 96 additions & 7 deletions TerraformPluginDotNet/Schemas/Types/TerraformTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using TerraformPluginDotNet.Resources;
using KeyAttribute = MessagePack.KeyAttribute;
using MessagePackObject = MessagePack.MessagePackObjectAttribute;

namespace TerraformPluginDotNet.Schemas.Types;

Expand Down Expand Up @@ -29,19 +30,102 @@ public TerraformType GetTerraformType(Type t)
return new TerraformType.TfBool();
}

var dictionaryType = t.GetInterfaces()
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>) && x.GenericTypeArguments[0] == typeof(string));
var genericType = t.IsGenericType ? t.GetGenericTypeDefinition() : null;

if (dictionaryType != null)
if (genericType == typeof(Tuple<>))
{
var valueType = GetTerraformType(t.GenericTypeArguments[1]);
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type));
}

if (genericType == typeof(Tuple<,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type));
}

if (genericType == typeof(Tuple<,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type));
}

if (genericType == typeof(Tuple<,,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
var el3Type = GetTerraformType(t.GenericTypeArguments[3]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type, el3Type));
}

if (genericType == typeof(Tuple<,,,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
var el3Type = GetTerraformType(t.GenericTypeArguments[3]);
var el4Type = GetTerraformType(t.GenericTypeArguments[4]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type, el3Type, el4Type));
}

if (genericType == typeof(Tuple<,,,,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
var el3Type = GetTerraformType(t.GenericTypeArguments[3]);
var el4Type = GetTerraformType(t.GenericTypeArguments[4]);
var el5Type = GetTerraformType(t.GenericTypeArguments[5]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type, el3Type, el4Type, el5Type));
}

if (genericType == typeof(Tuple<,,,,,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
var el3Type = GetTerraformType(t.GenericTypeArguments[3]);
var el4Type = GetTerraformType(t.GenericTypeArguments[4]);
var el5Type = GetTerraformType(t.GenericTypeArguments[5]);
var el6Type = GetTerraformType(t.GenericTypeArguments[6]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type, el3Type, el4Type, el5Type, el6Type));
}

if (genericType == typeof(Tuple<,,,,,,,>))
{
var el0Type = GetTerraformType(t.GenericTypeArguments[0]);
var el1Type = GetTerraformType(t.GenericTypeArguments[1]);
var el2Type = GetTerraformType(t.GenericTypeArguments[2]);
var el3Type = GetTerraformType(t.GenericTypeArguments[3]);
var el4Type = GetTerraformType(t.GenericTypeArguments[4]);
var el5Type = GetTerraformType(t.GenericTypeArguments[5]);
var el6Type = GetTerraformType(t.GenericTypeArguments[6]);
var el7Type = GetTerraformType(t.GenericTypeArguments[7]);
return new TerraformType.TfTuple(ImmutableList.Create(el0Type, el1Type, el2Type, el3Type, el4Type, el5Type, el6Type, el7Type));
}

var genericInterfaces = t.GetInterfaces()
.Where(x => x.IsGenericType)
.GroupBy(x => x.GetGenericTypeDefinition())
.ToDictionary(x => x.Key, x => x.First());

if (genericInterfaces.TryGetValue(typeof(IDictionary<,>), out var dictType))
{
var valueType = GetTerraformType(dictType.GenericTypeArguments[1]);
return new TerraformType.TfMap(valueType);
}

var collectionType = t.GetInterfaces()
.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
if (genericInterfaces.TryGetValue(typeof(ISet<>), out var setType))
{
var elementType = GetTerraformType(setType.GenericTypeArguments.Single());
return new TerraformType.TfSet(elementType);
}

if (collectionType != null)
if (genericInterfaces.TryGetValue(typeof(ICollection<>), out var collectionType))
{
var elementType = GetTerraformType(collectionType.GenericTypeArguments.Single());
return new TerraformType.TfList(elementType);
Expand All @@ -52,6 +136,11 @@ public TerraformType GetTerraformType(Type t)

private TerraformType GetTerraformTypeAsObject(Type t)
{
if (t.GetCustomAttribute<MessagePackObject>() == null)
{
throw new InvalidOperationException($"Type {t.Name} is represented as a Terraform object, but is missing a {nameof(MessagePackObject)} attribute.");
}

var properties = t.GetProperties();
var attrTypes = properties.ToDictionary(
prop => prop.GetCustomAttribute<KeyAttribute>()?.StringKey ?? throw new InvalidOperationException($"Missing {nameof(KeyAttribute)} on {prop.Name} in {t.Name}."),
Expand Down

0 comments on commit f1f2695

Please sign in to comment.