Resources are serialized as their URN, ID, and package version. Each Pulumi package is expected to register itself with the SDK. The package will be invoked to construct appropriate instances of rehydrated resources. Packages are distinguished by their name and their version. This is the foundation of cross-process resources. Related to #2430. Co-authored-by: Mikhail Shilkov <github@mikhail.io> Co-authored-by: Luke Hoban <luke@pulumi.com> Co-authored-by: Levi Blackstone <levi@pulumi.com>
99 lines
3.6 KiB
C#
99 lines
3.6 KiB
C#
// Copyright 2016-2019, Pulumi Corporation
|
|
|
|
using System;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using Google.Protobuf;
|
|
using Pulumi.Serialization;
|
|
|
|
namespace Pulumi
|
|
{
|
|
/// <summary>
|
|
/// Base type for all input argument classes.
|
|
/// </summary>
|
|
public abstract class InputArgs
|
|
{
|
|
private readonly ImmutableArray<InputInfo> _inputInfos;
|
|
|
|
private protected abstract void ValidateMember(Type memberType, string fullName);
|
|
|
|
protected InputArgs()
|
|
{
|
|
var fieldQuery =
|
|
from field in this.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
|
let attr = field.GetCustomAttribute<InputAttribute>()
|
|
where attr != null
|
|
select (attr, memberName: field.Name, memberType: field.FieldType, getValue: (Func<object, object?>)field.GetValue);
|
|
|
|
var propQuery =
|
|
from prop in this.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
|
|
let attr = prop.GetCustomAttribute<InputAttribute>()
|
|
where attr != null
|
|
select (attr, memberName: prop.Name, memberType: prop.PropertyType, getValue: (Func<object, object?>)prop.GetValue);
|
|
|
|
var all = fieldQuery.Concat(propQuery).ToList();
|
|
|
|
foreach (var (attr, memberName, memberType, getValue) in all)
|
|
{
|
|
var fullName = $"[Input] {this.GetType().FullName}.{memberName}";
|
|
ValidateMember(memberType, fullName);
|
|
}
|
|
|
|
_inputInfos = all.Select(t =>
|
|
new InputInfo(t.attr, t.memberName, t.memberType, t.getValue)).ToImmutableArray();
|
|
}
|
|
|
|
internal virtual async Task<ImmutableDictionary<string, object?>> ToDictionaryAsync()
|
|
{
|
|
var builder = ImmutableDictionary.CreateBuilder<string, object?>();
|
|
foreach (var info in _inputInfos)
|
|
{
|
|
var fullName = $"[Input] {this.GetType().FullName}.{info.MemberName}";
|
|
|
|
var value = info.GetValue(this);
|
|
if (info.Attribute.IsRequired && value == null)
|
|
{
|
|
throw new ArgumentNullException(info.MemberName, $"{fullName} is required but was not given a value");
|
|
}
|
|
|
|
if (info.Attribute.Json)
|
|
{
|
|
value = await ConvertToJsonAsync(fullName, value).ConfigureAwait(false);
|
|
}
|
|
|
|
builder.Add(info.Attribute.Name, value);
|
|
}
|
|
|
|
return builder.ToImmutable();
|
|
}
|
|
|
|
private async Task<object?> ConvertToJsonAsync(string context, object? input)
|
|
{
|
|
if (input == null)
|
|
return null;
|
|
|
|
var serializer = new Serializer(excessiveDebugOutput: false);
|
|
var obj = await serializer.SerializeAsync(context, input, false).ConfigureAwait(false);
|
|
var value = Serializer.CreateValue(obj);
|
|
return JsonFormatter.Default.Format(value);
|
|
}
|
|
|
|
private struct InputInfo
|
|
{
|
|
public readonly InputAttribute Attribute;
|
|
public readonly Type MemberType;
|
|
public readonly string MemberName;
|
|
public Func<object, object?> GetValue;
|
|
|
|
public InputInfo(InputAttribute attribute, string memberName, Type memberType, Func<object, object> getValue) : this()
|
|
{
|
|
Attribute = attribute;
|
|
MemberName = memberName;
|
|
MemberType = memberType;
|
|
GetValue = getValue;
|
|
}
|
|
}
|
|
}
|
|
}
|