Skip to content

Commit 2e7e671

Browse files
author
Christoph Bühler
committed
feat(transpiler): allow metadata to be created from actual types.
1 parent aca97e1 commit 2e7e671

File tree

7 files changed

+1206
-1043
lines changed

7 files changed

+1206
-1043
lines changed

src/KubeOps.Abstractions/Entities/EntityMetadata.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ public record EntityMetadata(string Kind, string Version, string? Group = null,
1717
/// <summary>
1818
/// Name of the singular entity.
1919
/// </summary>
20-
public string SingularName => Kind.ToLower();
20+
public string SingularName => Kind.ToLowerInvariant();
2121

2222
/// <summary>
2323
/// Name of the plural entity.
2424
/// </summary>
25-
public string PluralName => Plural ?? $"{Kind.ToLower()}s";
25+
public string PluralName => (Plural ?? $"{Kind}s").ToLowerInvariant();
26+
27+
public string GroupWithVersion => $"{Group ?? string.Empty}/{Version}".TrimStart('/');
2628
}

src/KubeOps.Transpiler/Entities.cs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ namespace KubeOps.Transpiler;
1313
public static class Entities
1414
{
1515
/// <summary>
16-
/// Create a metadata / scope tuple out of a given entity type.
16+
/// Create a metadata / scope tuple out of a given entity type via externally loaded assembly.
1717
/// </summary>
1818
/// <param name="context">The context that loaded the types.</param>
1919
/// <param name="entityType">The type to convert.</param>
2020
/// <returns>A tuple that contains <see cref="EntityMetadata"/> and a scope.</returns>
2121
/// <exception cref="ArgumentException">Thrown when the type contains no <see cref="KubernetesEntityAttribute"/>.</exception>
22-
public static (EntityMetadata Metadata, string Scope) ToEntityMetadata(this MetadataLoadContext context, Type entityType)
22+
public static (EntityMetadata Metadata, string Scope) ToEntityMetadata(
23+
this MetadataLoadContext context,
24+
Type entityType)
2325
=> (context.GetContextType(entityType).GetCustomAttributeData<KubernetesEntityAttribute>(),
2426
context.GetContextType(entityType).GetCustomAttributeData<EntityScopeAttribute>()) switch
2527
{
@@ -29,15 +31,45 @@ public static (EntityMetadata Metadata, string Scope) ToEntityMetadata(this Meta
2931
attr.GetCustomAttributeNamedArg<string>(context, nameof(KubernetesEntityAttribute.Kind)),
3032
entityType.Name),
3133
Defaulted(
32-
attr.GetCustomAttributeNamedArg<string>(context, nameof(KubernetesEntityAttribute.ApiVersion)),
34+
attr.GetCustomAttributeNamedArg<string>(
35+
context,
36+
nameof(KubernetesEntityAttribute.ApiVersion)),
3337
"v1"),
3438
attr.GetCustomAttributeNamedArg<string>(context, nameof(KubernetesEntityAttribute.Group)),
3539
attr.GetCustomAttributeNamedArg<string>(context, nameof(KubernetesEntityAttribute.PluralName))),
3640
scope switch
3741
{
38-
null => Enum.GetName(EntityScope.Namespaced) ?? "namespaced",
42+
null => Enum.GetName(EntityScope.Namespaced) ?? "Namespaced",
3943
_ => Enum.GetName(
40-
scope.GetCustomAttributeCtorArg<EntityScope>(context, 0)) ?? "namespaced",
44+
scope.GetCustomAttributeCtorArg<EntityScope>(context, 0)) ?? "Namespaced",
45+
}),
46+
};
47+
48+
/// <summary>
49+
/// Create a metadata / scope tuple out of a given entity type via reflection in the same loaded assembly.
50+
/// </summary>
51+
/// <param name="entityType">The type to convert.</param>
52+
/// <returns>A tuple that contains <see cref="EntityMetadata"/> and a scope.</returns>
53+
/// <exception cref="ArgumentException">Thrown when the type contains no <see cref="KubernetesEntityAttribute"/>.</exception>
54+
public static (EntityMetadata Metadata, string Scope) ToEntityMetadata(Type entityType)
55+
=> (entityType.GetCustomAttributeData<KubernetesEntityAttribute>(),
56+
entityType.GetCustomAttributeData<EntityScopeAttribute>()) switch
57+
{
58+
(null, _) => throw new ArgumentException("The given type is not a valid Kubernetes entity."),
59+
({ } attr, var scope) => (new(
60+
Defaulted(
61+
attr.GetCustomAttributeNamedArg<string>(nameof(KubernetesEntityAttribute.Kind)),
62+
entityType.Name),
63+
Defaulted(
64+
attr.GetCustomAttributeNamedArg<string>(nameof(KubernetesEntityAttribute.ApiVersion)),
65+
"v1"),
66+
attr.GetCustomAttributeNamedArg<string>(nameof(KubernetesEntityAttribute.Group)),
67+
attr.GetCustomAttributeNamedArg<string>(nameof(KubernetesEntityAttribute.PluralName))),
68+
scope switch
69+
{
70+
null => Enum.GetName(EntityScope.Namespaced) ?? "Namespaced",
71+
_ => Enum.GetName(
72+
scope.GetCustomAttributeCtorArg<EntityScope>(0)) ?? "Namespaced",
4173
}),
4274
};
4375

src/KubeOps.Transpiler/Utilities.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ public static T?
6161
? (T)attr.NamedArguments.FirstOrDefault(a => a.MemberName == name).TypedValue.Value!
6262
: default;
6363

64+
/// <summary>
65+
/// Load a specific named argument from a custom attribute.
66+
/// Without the metadata load context, this method is only usable for types in the same loaded assembly.
67+
/// Named arguments are in the property-notation:
68+
/// <c>[KubernetesEntity(Kind = "foobar")]</c>.
69+
/// </summary>
70+
/// <param name="attr">The attribute in question.</param>
71+
/// <param name="name">The name of the argument.</param>
72+
/// <typeparam name="T">What target type the argument has.</typeparam>
73+
/// <returns>The argument value if found.</returns>
74+
/// <exception cref="InvalidCastException">Thrown if the data did not match the target type.</exception>
75+
public static T?
76+
GetCustomAttributeNamedArg<T>(this CustomAttributeData attr, string name) =>
77+
attr.NamedArguments.FirstOrDefault(a => a.MemberName == name).TypedValue.ArgumentType == typeof(T)
78+
? (T)attr.NamedArguments.FirstOrDefault(a => a.MemberName == name).TypedValue.Value!
79+
: default;
80+
6481
/// <summary>
6582
/// Load a specific named argument array from a custom attribute.
6683
/// Named arguments are in the property-notation:
@@ -94,6 +111,23 @@ ReadOnlyCollection<CustomAttributeTypedArgument> value
94111
? (T)attr.ConstructorArguments[index].Value!
95112
: default;
96113

114+
/// <summary>
115+
/// Load a specific constructor argument from a custom attribute.
116+
/// Without the metadata load context, this method is only usable for types in the same loaded assembly.
117+
/// Constructor arguments are in the "new" format:
118+
/// <c>[KubernetesEntity("foobar")]</c>.
119+
/// </summary>
120+
/// <param name="attr">The attribute in question.</param>
121+
/// <param name="index">Index of the value in the constructor notation.</param>
122+
/// <typeparam name="T">What target type the argument has.</typeparam>
123+
/// <returns>The argument value if found.</returns>
124+
/// <exception cref="InvalidCastException">Thrown if the data did not match the target type.</exception>
125+
public static T? GetCustomAttributeCtorArg<T>(this CustomAttributeData attr, int index) =>
126+
attr.ConstructorArguments.Count >= index + 1 &&
127+
attr.ConstructorArguments[index].ArgumentType == typeof(T)
128+
? (T)attr.ConstructorArguments[index].Value!
129+
: default;
130+
97131
/// <summary>
98132
/// Load a specific constructor argument array from a custom attribute.
99133
/// Constructor arguments are in the "new" format:

0 commit comments

Comments
 (0)