public void GetResourceBodyCompletionSnippets_WithDiscriminatedObjectTypeAndNoRequiredProperties_ShouldReturnEmptySnippet() { SnippetsProvider snippetsProvider = new SnippetsProvider(); var objectTypeA = new ObjectType("objA", TypeSymbolValidationFlags.Default, new[] { new TypeProperty("discKey", new StringLiteralType("keyA")), new TypeProperty("keyAProp", LanguageConstants.String), }, null); var objectTypeB = new ObjectType("objB", TypeSymbolValidationFlags.Default, new[] { new TypeProperty("discKey", new StringLiteralType("keyB")), new TypeProperty("keyBProp", LanguageConstants.String), }, null); var discriminatedObjectType = new DiscriminatedObjectType("discObj", TypeSymbolValidationFlags.Default, "discKey", new[] { objectTypeA, objectTypeB }); TypeSymbol typeSymbol = new ResourceType( ResourceTypeReference.Parse("microsoft.aadiam/azureADMetrics@2020-07-01-preview"), ResourceScope.ResourceGroup, discriminatedObjectType); IEnumerable <Snippet> snippets = snippetsProvider.GetResourceBodyCompletionSnippets(typeSymbol, false); snippets.Should().SatisfyRespectively( x => { x.Prefix.Should().Be("{}"); x.Detail.Should().Be("{}"); x.CompletionPriority.Should().Be(CompletionPriority.Medium); x.Text.Should().Be("{\n\t$0\n}"); }); }
private static ResourceTypeComponents DiscriminatedPropertiesTestsType() { var resourceType = ResourceTypeReference.Parse("Test.Rp/discriminatedPropertiesTests@2020-01-01"); var propsA = new ObjectType( "PropertiesA", TypeSymbolValidationFlags.WarnOnTypeMismatch, new[] { new TypeProperty("propType", new StringLiteralType("PropertiesA"), TypePropertyFlags.None, "..."), new TypeProperty("propA", LanguageConstants.String, TypePropertyFlags.None, "This is the description for propA!"), }, null); var propsB = new ObjectType( "PropertiesB", TypeSymbolValidationFlags.WarnOnTypeMismatch, new[] { new TypeProperty("propType", new StringLiteralType("PropertiesB"), TypePropertyFlags.None, "..."), new TypeProperty("propB", LanguageConstants.String, TypePropertyFlags.None, "This is the description for propB!"), }, null); var propertiesType = new DiscriminatedObjectType( "properties", TypeSymbolValidationFlags.Default, "propType", new[] { propsA, propsB }); return(new ResourceTypeComponents(resourceType, ResourceScope.ResourceGroup, new ObjectType(resourceType.FormatName(), TypeSymbolValidationFlags.Default, GetCommonResourceProperties(resourceType).Concat(new[] { new TypeProperty("properties", propertiesType, TypePropertyFlags.Required, "properties property"), }), null))); }
private void HandlePolymorphicType(DiscriminatedObjectType discriminatedObjectType, CompositeType putType, CompositeType getType) { var putSubTypes = (putType != null ? codeModel.ModelTypes.Where(type => type.BaseModelType == putType) : Enumerable.Empty <CompositeType>()) .ToDictionary(x => x.SerializedName); var getSubTypes = (getType != null ? codeModel.ModelTypes.Where(type => type.BaseModelType == getType) : Enumerable.Empty <CompositeType>()) .ToDictionary(x => x.SerializedName); foreach (var subTypeName in putSubTypes.Keys.Concat(getSubTypes.Keys.Where(x => !putSubTypes.ContainsKey(x)))) { putSubTypes.TryGetValue(subTypeName, out var putSubType); getSubTypes.TryGetValue(subTypeName, out var getSubType); // Sub-types are never referenced directly in the Swagger // discriminator scenario, so they wouldn't be added to the // produced resource schema. By calling ParseCompositeType() on the // sub-type we add the sub-type to the resource schema. var polymorphicType = ParseCompositeType(putSubType, getSubType, false); if (namedDefinitions[subTypeName] is ObjectType objectType) { var discriminatorEnum = GetDiscriminatorType(putSubType, getSubType); objectType.Properties[discriminatedObjectType.Discriminator] = new ObjectProperty { Type = factory.GetReference(discriminatorEnum), Flags = ObjectPropertyFlags.Required, }; } discriminatedObjectType.Elements[subTypeName] = factory.GetReference(polymorphicType); } }
public static ResourceType SetBicepResourceProperties(ResourceType resourceType, bool isExistingResource) { var bodyType = resourceType.Body.Type; switch (bodyType) { case ObjectType bodyObjectType: bodyType = SetBicepResourceProperties(bodyObjectType, resourceType.ValidParentScopes, isExistingResource); break; case DiscriminatedObjectType bodyDiscriminatedType: var bodyTypes = bodyDiscriminatedType.UnionMembersByKey.Values.ToList() .Select(x => x.Type as ObjectType ?? throw new ArgumentException($"Resource {resourceType.Name} has unexpected body type {bodyType.GetType()}")); bodyTypes = bodyTypes.Select(x => SetBicepResourceProperties(x, resourceType.ValidParentScopes, isExistingResource)); bodyType = new DiscriminatedObjectType( bodyDiscriminatedType.Name, bodyDiscriminatedType.ValidationFlags, bodyDiscriminatedType.DiscriminatorKey, bodyTypes); break; default: // we exhaustively test deserialization of every resource type during CI, and this happens in a deterministic fashion, // so this exception should never occur in the released product throw new ArgumentException($"Resource {resourceType.Name} has unexpected body type {bodyType.GetType()}"); } return(new ResourceType(resourceType.TypeReference, resourceType.ValidParentScopes, bodyType)); }
public void GetResourceBodyCompletionSnippets_WithDiscriminatedObjectTypeAndRequiredProperties_ShouldReturnRequiredPropertiesSnippet() { SnippetsProvider snippetsProvider = new SnippetsProvider(); var objectTypeA = new ObjectType("objA", TypeSymbolValidationFlags.Default, new[] { new TypeProperty("discKey", new StringLiteralType("keyA")), new TypeProperty("name", new StringLiteralType("keyA"), TypePropertyFlags.Required), new TypeProperty("location", LanguageConstants.String, TypePropertyFlags.Required), new TypeProperty("id", LanguageConstants.String) }, null); var objectTypeB = new ObjectType("objB", TypeSymbolValidationFlags.Default, new[] { new TypeProperty("discKey", new StringLiteralType("keyB")), new TypeProperty("name", LanguageConstants.String, TypePropertyFlags.Required), new TypeProperty("kind", new StringLiteralType("discKey"), TypePropertyFlags.ReadOnly), new TypeProperty("hostPoolType", LanguageConstants.String) }, null); var discriminatedObjectType = new DiscriminatedObjectType("discObj", TypeSymbolValidationFlags.Default, "discKey", new[] { objectTypeA, objectTypeB }); TypeSymbol typeSymbol = new ResourceType( ResourceTypeReference.Parse("microsoft.aadiam/azureADMetrics@2020-07-01-preview"), ResourceScope.ResourceGroup, discriminatedObjectType); IEnumerable <Snippet> snippets = snippetsProvider.GetResourceBodyCompletionSnippets(typeSymbol, false); snippets.Should().SatisfyRespectively( x => { x.Prefix.Should().Be("{}"); x.Detail.Should().Be("{}"); x.CompletionPriority.Should().Be(CompletionPriority.Medium); x.Text.Should().Be("{\n\t$0\n}"); }, x => { x.Prefix.Should().Be("required-properties-keyA"); x.Detail.Should().Be("Required properties"); x.CompletionPriority.Should().Be(CompletionPriority.Medium); x.Text.Should().BeEquivalentToIgnoringNewlines(@"{ name: 'keyA' location: $1 $0 }"); }, x => { x.Prefix.Should().Be("required-properties-keyB"); x.Detail.Should().Be("Required properties"); x.CompletionPriority.Should().Be(CompletionPriority.Medium); x.Text.Should().BeEquivalentToIgnoringNewlines(@"{ name: $1 $0 }"); }); }
public override void Write(Utf8JsonWriter writer, TypeBase value, JsonSerializerOptions options) { writer.WriteStartObject(); var propertyEnum = value switch { BuiltInType _ => TypeBaseKind.BuiltInType, ObjectType _ => TypeBaseKind.ObjectType, ArrayType _ => TypeBaseKind.ArrayType, ResourceType _ => TypeBaseKind.ResourceType, UnionType _ => TypeBaseKind.UnionType, StringLiteralType _ => TypeBaseKind.StringLiteralType, DiscriminatedObjectType _ => TypeBaseKind.DiscriminatedObjectType, ResourceFunctionType _ => TypeBaseKind.ResourceFunctionType, _ => throw new JsonException(), }; writer.WritePropertyName(((int)propertyEnum).ToString()); switch (value) { case BuiltInType builtInType: JsonSerializer.Serialize(writer, builtInType, serializerOptions); break; case ObjectType objectType: JsonSerializer.Serialize(writer, objectType, serializerOptions); break; case ArrayType arrayType: JsonSerializer.Serialize(writer, arrayType, serializerOptions); break; case ResourceType resourceType: JsonSerializer.Serialize(writer, resourceType, serializerOptions); break; case UnionType unionType: JsonSerializer.Serialize(writer, unionType, serializerOptions); break; case StringLiteralType stringLiteralType: JsonSerializer.Serialize(writer, stringLiteralType, serializerOptions); break; case DiscriminatedObjectType discriminatedObjectType: JsonSerializer.Serialize(writer, discriminatedObjectType, serializerOptions); break; case ResourceFunctionType resourceFunctionType: JsonSerializer.Serialize(writer, resourceFunctionType, serializerOptions); break; default: throw new JsonException(); } writer.WriteEndObject(); }
private void WriteKeywordNestedFunctionDefinitions(DiscriminatedObjectType discriminatedType, string discriminatorName) { // Define the body and discriminator parameters // Define all the common base properties as functions bool needNewline = false; foreach (KeyValuePair <string, ObjectProperty> baseProperty in discriminatedType.BaseProperties) { if (needNewline) { _writer.WriteLine(); } WriteKeywordDefinition(baseProperty.Key, baseProperty.Value.Type.Type); needNewline = true; } WriteDiscriminatedKeywordDefinitions(discriminatorName, discriminatedType.Discriminator, (IReadOnlyDictionary <string, ITypeReference>)discriminatedType.Elements); }
public void DiscriminatedObjectType_should_be_correctly_instantiated() { var objectA = new ObjectType("objA", TypeSymbolValidationFlags.Default, new [] { new TypeProperty("discKey", new StringLiteralType("keyA")), new TypeProperty("keyAProp", LanguageConstants.String), }, null); var objectB = new ObjectType("objB", TypeSymbolValidationFlags.Default, new [] { new TypeProperty("discKey", new StringLiteralType("keyB")), new TypeProperty("keyBProp", LanguageConstants.String), }, null); var discObj = new DiscriminatedObjectType("discObj", TypeSymbolValidationFlags.Default, "discKey", new [] { objectA, objectB }); discObj.UnionMembersByKey.Keys.Should().BeEquivalentTo("'keyA'", "'keyB'"); discObj.TypeKind.Should().Be(TypeKind.DiscriminatedObject); discObj.UnionMembersByKey[new StringLiteralType("keyA").Name].Type.Should().Be(objectA); discObj.UnionMembersByKey[new StringLiteralType("keyB").Name].Type.Should().Be(objectB); }
private void WriteKeywordDefinitionBody(string armKey, DiscriminatedObjectType discriminatedType, int arrayDepth) { // For discriminated objects, we assume we basically have a base object // and a list of conceptual "subclasses" each with an associated discriminator. // We solve this as follows: // - Add the "discriminator" as a mandatory enum parameter // - Define the functions for the base properties // - Conditionally define the subclass properties // - Invoke the call as normal string discriminatorName = SanitizeStringForVariableName(discriminatedType.Discriminator); WriteKeywordBodyParamBlock(discriminatorName, (IReadOnlyCollection <string>)discriminatedType.Elements.Keys); WriteKeywordNestedFunctionDefinitions(discriminatedType, discriminatorName); WritePrimitiveInvocation( armKey, KeywordArgumentKind.Body, arrayDepth, discriminatorKey: discriminatedType.Discriminator, discriminatorVariable: discriminatorName); }
private static ResourceTypeComponents DiscriminatorTestsType() { var resourceType = ResourceTypeReference.Parse("Test.Rp/discriminatorTests@2020-01-01"); var bodyAProps = new ObjectType( "BodyAProperties", TypeSymbolValidationFlags.WarnOnTypeMismatch, new[] { new TypeProperty("propA", LanguageConstants.String, TypePropertyFlags.None, "This is the description for propA!"), }, null); var bodyBProps = new ObjectType( "BodyBProperties", TypeSymbolValidationFlags.WarnOnTypeMismatch, new[] { new TypeProperty("propB", LanguageConstants.String, TypePropertyFlags.None, "This is the description for propB!"), }, null); var bodyType = new DiscriminatedObjectType( resourceType.FormatName(), TypeSymbolValidationFlags.Default, "kind", new[] { new ObjectType("BodyA", TypeSymbolValidationFlags.Default, GetCommonResourceProperties(resourceType).Concat(new [] { new TypeProperty("kind", new StringLiteralType("BodyA"), TypePropertyFlags.None, "This is the kind of body A"), new TypeProperty("properties", bodyAProps, TypePropertyFlags.None, "These are the properties for body A"), }), null), new ObjectType("BodyB", TypeSymbolValidationFlags.Default, GetCommonResourceProperties(resourceType).Concat(new [] { new TypeProperty("kind", new StringLiteralType("BodyB"), TypePropertyFlags.None, "This is the kind of body B"), new TypeProperty("properties", bodyBProps, TypePropertyFlags.None, "These are the properties for body B"), }), null), }); return(new ResourceTypeComponents(resourceType, ResourceScope.ResourceGroup, bodyType)); }
static PropertySymbol?GetPropertySymbol(TypeSymbol?baseType, string property) { if (baseType is null) { return(null); } var typeProperty = TypeAssignmentVisitor.UnwrapType(baseType) switch { ObjectType x => x.Properties.TryGetValue(property, out var tp) ? tp : null, DiscriminatedObjectType x => x.TryGetDiscriminatorProperty(property), _ => null }; if (typeProperty is null) { return(null); } return(new PropertySymbol(property, typeProperty.Description, typeProperty.TypeReference.Type)); } switch (syntax) { case InstanceFunctionCallSyntax ifc: { var baseType = GetTypeInfo(ifc.BaseExpression); switch (baseType) { case NamespaceType namespaceType when SyntaxTree.Hierarchy.GetParent(ifc) is DecoratorSyntax: return(namespaceType.DecoratorResolver.TryGetSymbol(ifc.Name)); case ObjectType objectType: return(objectType.MethodResolver.TryGetSymbol(ifc.Name)); } return(null); } case PropertyAccessSyntax propertyAccess: { var baseType = GetDeclaredType(propertyAccess.BaseExpression); var property = propertyAccess.PropertyName.IdentifierName; return(GetPropertySymbol(baseType, property)); } case ObjectPropertySyntax objectProperty: { if (Binder.GetParent(objectProperty) is not { } parentSyntax) { return(null); } var baseType = GetDeclaredType(parentSyntax); if (objectProperty.TryGetKeyText() is not { } property) { return(null); } return(GetPropertySymbol(baseType, property)); } } return(this.Binder.GetSymbolInfo(syntax)); }
public void DiscriminatedObjectType_raises_appropriate_diagnostics_for_matches() { var discriminatedType = new DiscriminatedObjectType( "discObj", "myDiscriminator", new [] { new NamedObjectType("typeA", new [] { new TypeProperty("myDiscriminator", new StringLiteralType("valA")), new TypeProperty("fieldA", LanguageConstants.Any, TypePropertyFlags.Required), }, null), new NamedObjectType("typeB", new [] { new TypeProperty("myDiscriminator", new StringLiteralType("valB")), new TypeProperty("fieldB", LanguageConstants.Any, TypePropertyFlags.Required), }, null), }); // no discriminator field supplied var obj = TestSyntaxFactory.CreateObject(new [] { TestSyntaxFactory.CreateProperty("fieldA", TestSyntaxFactory.CreateString("someVal")), }); var errors = TypeValidator.GetExpressionAssignmentDiagnostics(CreateTypeManager(), obj, discriminatedType); errors.Should().SatisfyRespectively( x => { x.Message.Should().Be("The property 'myDiscriminator' requires a value of type 'valA' | 'valB', but none was supplied."); }); // incorrect type specified for the discriminator field obj = TestSyntaxFactory.CreateObject(new [] { TestSyntaxFactory.CreateProperty("myDiscriminator", TestSyntaxFactory.CreateObject(Enumerable.Empty <ObjectPropertySyntax>())), TestSyntaxFactory.CreateProperty("fieldB", TestSyntaxFactory.CreateString("someVal")), }); errors = TypeValidator.GetExpressionAssignmentDiagnostics(CreateTypeManager(), obj, discriminatedType); errors.Should().SatisfyRespectively( x => { x.Message.Should().Be("The property 'myDiscriminator' expected a value of type 'valA' | 'valB' but the provided value is of type object."); }); // discriminator value that matches neither option supplied obj = TestSyntaxFactory.CreateObject(new [] { TestSyntaxFactory.CreateProperty("myDiscriminator", TestSyntaxFactory.CreateString("valC")), }); errors = TypeValidator.GetExpressionAssignmentDiagnostics(CreateTypeManager(), obj, discriminatedType); errors.Should().SatisfyRespectively( x => { x.Message.Should().Be("The property 'myDiscriminator' expected a value of type 'valA' | 'valB' but the provided value is of type 'valC'."); }); // missing required property for the 'valB' branch obj = TestSyntaxFactory.CreateObject(new [] { TestSyntaxFactory.CreateProperty("myDiscriminator", TestSyntaxFactory.CreateString("valB")), }); errors = TypeValidator.GetExpressionAssignmentDiagnostics(CreateTypeManager(), obj, discriminatedType); errors.Should().SatisfyRespectively( x => { x.Message.Should().Be("The specified object is missing the following required properties: fieldB."); }); // supplied the required property for the 'valB' branch obj = TestSyntaxFactory.CreateObject(new [] { TestSyntaxFactory.CreateProperty("myDiscriminator", TestSyntaxFactory.CreateString("valB")), TestSyntaxFactory.CreateProperty("fieldB", TestSyntaxFactory.CreateString("someVal")), }); errors = TypeValidator.GetExpressionAssignmentDiagnostics(CreateTypeManager(), obj, discriminatedType); errors.Should().BeEmpty(); }
protected abstract TResult VisitDiscriminatedObject(DiscriminatedObjectType armDiscriminatedObject);
protected override DslKeywordSchema VisitDiscriminatedObject(DiscriminatedObjectType armDiscriminatedObject) { return(new BicepDiscriminatedObjectKeywordSchema(armDiscriminatedObject)); }
protected override IReadOnlyDictionary <string, DslParameterInfo> VisitDiscriminatedObject(DiscriminatedObjectType armDiscriminatedObject) { return(s_bodyParameters); }