* Resource package registration in .NET * Refactor to attribute-based discovery * Refactor to derived attributes
255 lines
11 KiB
C#
255 lines
11 KiB
C#
// Copyright 2016-2019, Pulumi Corporation
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Google.Protobuf.Collections;
|
|
using Google.Protobuf.WellKnownTypes;
|
|
|
|
namespace Pulumi.Serialization
|
|
{
|
|
internal static class Deserializer
|
|
{
|
|
private static OutputData<T> DeserializeCore<T>(Value value, Func<Value, OutputData<T>> func)
|
|
{
|
|
var (innerVal, isSecret) = UnwrapSecret(value);
|
|
value = innerVal;
|
|
|
|
if (value.KindCase == Value.KindOneofCase.StringValue &&
|
|
value.StringValue == Constants.UnknownValue)
|
|
{
|
|
// always deserialize unknown as the null value.
|
|
return new OutputData<T>(ImmutableHashSet<Resource>.Empty, default!, isKnown: false, isSecret);
|
|
}
|
|
|
|
if (TryDeserializeAssetOrArchive(value, out var assetOrArchive))
|
|
{
|
|
return new OutputData<T>(ImmutableHashSet<Resource>.Empty, (T)(object)assetOrArchive, isKnown: true, isSecret);
|
|
}
|
|
else if (TryDeserializeResource(value, out var resource))
|
|
{
|
|
return new OutputData<T>(ImmutableHashSet<Resource>.Empty, (T)(object)resource, isKnown: true, isSecret);
|
|
}
|
|
|
|
var innerData = func(value);
|
|
return OutputData.Create(innerData.Resources, innerData.Value, innerData.IsKnown, isSecret || innerData.IsSecret);
|
|
}
|
|
|
|
private static OutputData<T> DeserializeOneOf<T>(Value value, Value.KindOneofCase kind, Func<Value, OutputData<T>> func)
|
|
=> DeserializeCore(value, v =>
|
|
v.KindCase == kind ? func(v) : throw new InvalidOperationException($"Trying to deserialize {v.KindCase} as a {kind}"));
|
|
|
|
private static OutputData<T> DeserializePrimitive<T>(Value value, Value.KindOneofCase kind, Func<Value, T> func)
|
|
=> DeserializeOneOf(value, kind, v => OutputData.Create(
|
|
ImmutableHashSet<Resource>.Empty, func(v), isKnown: true, isSecret: false));
|
|
|
|
private static OutputData<bool> DeserializeBoolean(Value value)
|
|
=> DeserializePrimitive(value, Value.KindOneofCase.BoolValue, v => v.BoolValue);
|
|
|
|
private static OutputData<string> DeserializerString(Value value)
|
|
=> DeserializePrimitive(value, Value.KindOneofCase.StringValue, v => v.StringValue);
|
|
|
|
private static OutputData<double> DeserializerDouble(Value value)
|
|
=> DeserializePrimitive(value, Value.KindOneofCase.NumberValue, v => v.NumberValue);
|
|
|
|
private static OutputData<ImmutableArray<object?>> DeserializeList(Value value)
|
|
=> DeserializeOneOf(value, Value.KindOneofCase.ListValue,
|
|
v =>
|
|
{
|
|
var resources = ImmutableHashSet.CreateBuilder<Resource>();
|
|
var result = ImmutableArray.CreateBuilder<object?>();
|
|
var isKnown = true;
|
|
var isSecret = false;
|
|
|
|
foreach (var element in v.ListValue.Values)
|
|
{
|
|
var elementData = Deserialize(element);
|
|
(isKnown, isSecret) = OutputData.Combine(elementData, isKnown, isSecret);
|
|
resources.UnionWith(elementData.Resources);
|
|
result.Add(elementData.Value);
|
|
}
|
|
|
|
return OutputData.Create(resources.ToImmutable(), result.ToImmutable(), isKnown, isSecret);
|
|
});
|
|
|
|
private static OutputData<ImmutableDictionary<string, object?>> DeserializeStruct(Value value)
|
|
=> DeserializeOneOf(value, Value.KindOneofCase.StructValue,
|
|
v =>
|
|
{
|
|
var resources = ImmutableHashSet.CreateBuilder<Resource>();
|
|
var result = ImmutableDictionary.CreateBuilder<string, object?>();
|
|
var isKnown = true;
|
|
var isSecret = false;
|
|
|
|
foreach (var (key, element) in v.StructValue.Fields)
|
|
{
|
|
// Unilaterally skip properties considered internal by the Pulumi engine.
|
|
// These don't actually contribute to the exposed shape of the object, do
|
|
// not need to be passed back to the engine, and often will not match the
|
|
// expected type we are deserializing into.
|
|
if (key.StartsWith("__", StringComparison.Ordinal))
|
|
continue;
|
|
|
|
var elementData = Deserialize(element);
|
|
(isKnown, isSecret) = OutputData.Combine(elementData, isKnown, isSecret);
|
|
result.Add(key, elementData.Value);
|
|
resources.UnionWith(elementData.Resources);
|
|
}
|
|
|
|
return OutputData.Create(resources.ToImmutable(), result.ToImmutable(), isKnown, isSecret);
|
|
});
|
|
|
|
public static OutputData<object?> Deserialize(Value value)
|
|
=> DeserializeCore(value,
|
|
v => v.KindCase switch
|
|
{
|
|
Value.KindOneofCase.NumberValue => DeserializerDouble(v),
|
|
Value.KindOneofCase.StringValue => DeserializerString(v),
|
|
Value.KindOneofCase.BoolValue => DeserializeBoolean(v),
|
|
Value.KindOneofCase.StructValue => DeserializeStruct(v),
|
|
Value.KindOneofCase.ListValue => DeserializeList(v),
|
|
Value.KindOneofCase.NullValue => new OutputData<object?>(ImmutableHashSet<Resource>.Empty, null, isKnown: true, isSecret: false),
|
|
Value.KindOneofCase.None => throw new InvalidOperationException("Should never get 'None' type when deserializing protobuf"),
|
|
_ => throw new InvalidOperationException("Unknown type when deserializing protobuf: " + v.KindCase),
|
|
});
|
|
|
|
private static (Value unwrapped, bool isSecret) UnwrapSecret(Value value)
|
|
{
|
|
var isSecret = false;
|
|
|
|
while (IsSpecialStruct(value, out var sig) &&
|
|
sig == Constants.SpecialSecretSig)
|
|
{
|
|
if (!value.StructValue.Fields.TryGetValue(Constants.SecretValueName, out var secretValue))
|
|
throw new InvalidOperationException("Secrets must have a field called 'value'");
|
|
|
|
isSecret = true;
|
|
value = secretValue;
|
|
}
|
|
|
|
return (value, isSecret);
|
|
}
|
|
|
|
private static bool IsSpecialStruct(
|
|
Value value, [NotNullWhen(true)] out string? sig)
|
|
{
|
|
if (value.KindCase == Value.KindOneofCase.StructValue &&
|
|
value.StructValue.Fields.TryGetValue(Constants.SpecialSigKey, out var sigVal) &&
|
|
sigVal.KindCase == Value.KindOneofCase.StringValue)
|
|
{
|
|
sig = sigVal.StringValue;
|
|
return true;
|
|
}
|
|
|
|
sig = null;
|
|
return false;
|
|
}
|
|
|
|
private static bool TryDeserializeAssetOrArchive(
|
|
Value value, [NotNullWhen(true)] out AssetOrArchive? assetOrArchive)
|
|
{
|
|
if (IsSpecialStruct(value, out var sig))
|
|
{
|
|
if (sig == Constants.SpecialAssetSig)
|
|
{
|
|
assetOrArchive = DeserializeAsset(value);
|
|
return true;
|
|
}
|
|
else if (sig == Constants.SpecialArchiveSig)
|
|
{
|
|
assetOrArchive = DeserializeArchive(value);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
assetOrArchive = null;
|
|
return false;
|
|
}
|
|
|
|
private static Archive DeserializeArchive(Value value)
|
|
{
|
|
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchivePathName, out var path))
|
|
return new FileArchive(path);
|
|
|
|
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchiveUriName, out var uri))
|
|
return new RemoteArchive(uri);
|
|
|
|
if (value.StructValue.Fields.TryGetValue(Constants.ArchiveAssetsName, out var assetsValue))
|
|
{
|
|
if (assetsValue.KindCase == Value.KindOneofCase.StructValue)
|
|
{
|
|
var assets = ImmutableDictionary.CreateBuilder<string, AssetOrArchive>();
|
|
foreach (var (name, val) in assetsValue.StructValue.Fields)
|
|
{
|
|
if (!TryDeserializeAssetOrArchive(val, out var innerAssetOrArchive))
|
|
throw new InvalidOperationException("AssetArchive contained an element that wasn't itself an Asset or Archive.");
|
|
|
|
assets[name] = innerAssetOrArchive;
|
|
}
|
|
|
|
return new AssetArchive(assets.ToImmutable());
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("Value was marked as Archive, but did not conform to required shape.");
|
|
}
|
|
|
|
private static Asset DeserializeAsset(Value value)
|
|
{
|
|
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchivePathName, out var path))
|
|
return new FileAsset(path);
|
|
|
|
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetOrArchiveUriName, out var uri))
|
|
return new RemoteAsset(uri);
|
|
|
|
if (TryGetStringValue(value.StructValue.Fields, Constants.AssetTextName, out var text))
|
|
return new StringAsset(text);
|
|
|
|
throw new InvalidOperationException("Value was marked as Asset, but did not conform to required shape.");
|
|
}
|
|
|
|
private static bool TryDeserializeResource(
|
|
Value value, [NotNullWhen(true)] out Resource? resource)
|
|
{
|
|
if (!IsSpecialStruct(value, out var sig) || sig != Constants.SpecialResourceSig)
|
|
{
|
|
resource = null;
|
|
return false;
|
|
}
|
|
|
|
string? urn;
|
|
if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceUrnName, out urn))
|
|
{
|
|
throw new InvalidOperationException("Value was marked as a Resource, but did not conform to required shape.");
|
|
}
|
|
|
|
string? version;
|
|
if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceVersionName, out version)) {
|
|
version = "";
|
|
}
|
|
|
|
var urnParts = urn.Split("::");
|
|
var qualifiedType = urnParts[2];
|
|
var qualifiedTypeParts = qualifiedType.Split('$');
|
|
var type = qualifiedTypeParts[^1];
|
|
|
|
resource = ResourcePackages.Construct(type, version, urn);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryGetStringValue(
|
|
MapField<string, Value> fields, string keyName, [NotNullWhen(true)] out string? result)
|
|
{
|
|
if (fields.TryGetValue(keyName, out var value) &&
|
|
value.KindCase == Value.KindOneofCase.StringValue)
|
|
{
|
|
result = value.StringValue;
|
|
return true;
|
|
}
|
|
|
|
result = null;
|
|
return false;
|
|
}
|
|
}
|
|
}
|