/// <summary>
        /// Adds the type name annotations required for proper json light serialization.
        /// </summary>
        /// <param name="value">The collection value for which the annotations have to be added.</param>
        /// <param name="metadataLevel">The OData metadata level of the response.</param>
        protected internal static void AddTypeNameAnnotationAsNeeded(ODataCollectionValue value, ODataMetadataLevel metadataLevel)
        {
            // ODataLib normally has the caller decide whether or not to serialize properties by leaving properties
            // null when values should not be serialized. The TypeName property is different and should always be
            // provided to ODataLib to enable model validation. A separate annotation is used to decide whether or not
            // to serialize the type name (a null value prevents serialization).

            // Note that this annotation should not be used for Atom or JSON verbose formats, as it will interfere with
            // the correct default behavior for those formats.

            Contract.Assert(value != null);

            // Only add an annotation if we want to override ODataLib's default type name serialization behavior.
            if (ShouldAddTypeNameAnnotation(metadataLevel))
            {
                string typeName;

                // Provide the type name to serialize (or null to force it not to serialize).
                if (ShouldSuppressTypeNameSerialization(metadataLevel))
                {
                    typeName = null;
                }
                else
                {
                    typeName = value.TypeName;
                }

                value.SetAnnotation<SerializationTypeNameAnnotation>(new SerializationTypeNameAnnotation
                {
                    TypeName = typeName
                });
            }
        }
        public void WriteInstanceAnnotation_ForCollectionShouldUseCollectionCodePath()
        {
            var collectionValue = new ODataCollectionValue() { TypeName = "Collection(String)" };
            collectionValue.SetAnnotation(new SerializationTypeNameAnnotation() { TypeName = null });
            const string term = "some.term";
            var verifierCalls = 0;

            this.jsonWriter.WriteNameVerifier = (name) =>
            {
                name.Should().Be("@" + term);
                verifierCalls++;
            };
            this.valueWriter.WriteCollectionVerifier = (value, reference, isTopLevelProperty, isInUri, isOpenProperty) =>
            {
                value.Should().Be(collectionValue);
                reference.Should().BeNull();
                isOpenProperty.Should().BeTrue();
                isTopLevelProperty.Should().BeFalse();
                isInUri.Should().BeFalse();
                verifierCalls.Should().Be(1);
                verifierCalls++;
            };
            this.jsonLightInstanceAnnotationWriter.WriteInstanceAnnotation(new ODataInstanceAnnotation(term, collectionValue));
            verifierCalls.Should().Be(2);
        }
 public void TypeNameShouldComeFromSerializationTypeNameAnnotationForCollectionValue()
 {
     var stna = new SerializationTypeNameAnnotation() {TypeName = "FromSTNA"};
     var value = new ODataCollectionValue() {TypeName = "Collection(Edm.String)"};
     value.SetAnnotation(stna);
     this.typeNameOracle.GetValueTypeNameForWriting(value,
         EdmCoreModel.GetCollection(EdmCoreModel.Instance.GetString(true)),
         EdmCoreModel.GetCollection(EdmCoreModel.Instance.GetString(false)),
         /* isOpenProperty*/ false).Should().Be("FromSTNA");
 }
        public void CollectionValueSerializationTypeNameAnnotationTest()
        {
            #region test cases

            var testCases = new[]
            {
                new
                {
                    TypeName = (string)null,
                    SerializationTypeNameAnnotation = (SerializationTypeNameAnnotation)null,
                    XmlTypeName = MissingTypeNameSentinelTextAtom,
                    JsonTypeName = MissingTypeNameSentinelTextJson,
                    JsonLightTypeName = MissingTypeNameSentinelTextJson,
                    ExpectedExceptionInJsonLight = (object)ODataExpectedExceptions.ODataException("WriterValidationUtils_MissingTypeNameWithMetadata"),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = (object)ODataExpectedExceptions.ODataException("ODataContextUriBuilder_TypeNameMissingForProperty"),
                    ExpectedExceptionInAtomForResponse = (object)ODataExpectedExceptions.ODataException("ODataContextUriBuilder_TypeNameMissingForProperty"),
                },
                new
                {
                    TypeName = EntityModelUtils.GetCollectionTypeName("Edm.Int32"),
                    SerializationTypeNameAnnotation = (SerializationTypeNameAnnotation)null,
                    XmlTypeName = "<typeName>" + EntityModelUtils.GetCollectionTypeName("Edm.Int32") + "</typeName>",
                    JsonTypeName = "\"type\":\"" + EntityModelUtils.GetCollectionTypeName("Edm.Int32") + "\"",
                    JsonLightTypeName = MissingTypeNameSentinelTextJson,
                    ExpectedExceptionInJsonLight = new object(),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = new object(),
                    ExpectedExceptionInAtomForResponse = new object(),
                },
                new
                {
                    TypeName = EntityModelUtils.GetCollectionTypeName("Edm.Int32"),
                    SerializationTypeNameAnnotation = new SerializationTypeNameAnnotation() { TypeName = null },
                    XmlTypeName = MissingTypeNameSentinelTextAtom,
                    JsonTypeName = MissingTypeNameSentinelTextJson,
                    JsonLightTypeName = MissingTypeNameSentinelTextJson,
                    ExpectedExceptionInJsonLight = new object(),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = new object(),
                    ExpectedExceptionInAtomForResponse = new object(),
                },
                new
                {
                    TypeName = (string)null,
                    SerializationTypeNameAnnotation = new SerializationTypeNameAnnotation() { TypeName = EntityModelUtils.GetCollectionTypeName("Edm.String") },
                    XmlTypeName = "<typeName>" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "</typeName>",
                    JsonTypeName = "\"type\":\"" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "\"",
                    JsonLightTypeName = "\"@odata.type\":\"#" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "\"",
                    ExpectedExceptionInJsonLight = (object)ODataExpectedExceptions.ODataException("WriterValidationUtils_MissingTypeNameWithMetadata"),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = (object)ODataExpectedExceptions.ODataException("ODataJsonLightValueSerializer_MissingTypeNameOnCollection"),
                    ExpectedExceptionInAtomForResponse = new object(),
                },
                new
                {
                    TypeName = (string)null,
                    SerializationTypeNameAnnotation = new SerializationTypeNameAnnotation() { TypeName = string.Empty },
                    XmlTypeName = "<typeName></typeName>",
                    JsonTypeName = "\"type\":\"\"",
                    JsonLightTypeName = "\"@odata.type\":\"\"",
                    ExpectedExceptionInJsonLight = (object)ODataExpectedExceptions.ODataException("WriterValidationUtils_MissingTypeNameWithMetadata"),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = (object)ODataExpectedExceptions.ODataException("ODataContextUriBuilder_TypeNameMissingForProperty"),
                    ExpectedExceptionInAtomForResponse = (object)ODataExpectedExceptions.ODataException("ODataContextUriBuilder_TypeNameMissingForProperty"),
                },
                new
                {
                    TypeName = (string)null,
                    SerializationTypeNameAnnotation = new SerializationTypeNameAnnotation() { TypeName = "NonCollectionTypeName" },
                    XmlTypeName = "<typeName>NonCollectionTypeName</typeName>",
                    JsonTypeName = "\"type\":\"NonCollectionTypeName\"",
                    JsonLightTypeName = "\"@odata.type\":\"#NonCollectionTypeName\"",
                    ExpectedExceptionInJsonLight = (object)ODataExpectedExceptions.ODataException("WriterValidationUtils_MissingTypeNameWithMetadata"),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = (object)ODataExpectedExceptions.ODataException("ODataJsonLightValueSerializer_MissingTypeNameOnCollection"),
                    ExpectedExceptionInAtomForResponse = new object(),
                },
                new
                {
                    TypeName = EntityModelUtils.GetCollectionTypeName("Edm.Int32"),
                    SerializationTypeNameAnnotation = new SerializationTypeNameAnnotation() { TypeName = EntityModelUtils.GetCollectionTypeName("Edm.String") },
                    XmlTypeName = "<typeName>" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "</typeName>",
                    JsonTypeName = "\"type\":\"" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "\"",
                    JsonLightTypeName = "\"@odata.type\":\"#" + EntityModelUtils.GetCollectionTypeName("Edm.String") + "\"",
                    ExpectedExceptionInJsonLight = new object(),
                    ExpectedExceptionInAtom = new object(),
                    ExpectedExceptionInJsonLightForResponse = new object(),
                    ExpectedExceptionInAtomForResponse = new object(),
                },
            };
            #endregion test cases

            var testDescriptors = testCases.Select(tc =>
            {
                EdmModel model = new EdmModel();

                var owningEntityType = new EdmEntityType("TestNS", "OwningEntityType");
                owningEntityType.AddStructuralProperty("PropertyName", EdmCoreModel.GetCollection(EdmCoreModel.Instance.GetInt32(isNullable: false)));
                model.AddElement(owningEntityType);

                var container = new EdmEntityContainer("TestNS", "TestContainer");
                model.AddElement(container);

                ODataComplexValue complexValue = new ODataComplexValue();
                complexValue.TypeName = tc.TypeName;
                complexValue.Properties = new[] { new ODataProperty() { Name = "TestProperty", Value = "TestValue" } };
                if (tc.SerializationTypeNameAnnotation != null)
                {
                    complexValue.SetAnnotation(tc.SerializationTypeNameAnnotation);
                }

                ODataCollectionValue collection = new ODataCollectionValue();
                collection.TypeName = tc.TypeName;
                if (tc.SerializationTypeNameAnnotation != null)
                {
                    collection.SetAnnotation(tc.SerializationTypeNameAnnotation);
                }

                return new PayloadWriterTestDescriptor<ODataProperty>(
                    this.Settings,
                    new ODataProperty { Name = "PropertyName", Value = collection },
                    (testConfiguration) =>
                    {
                        if (testConfiguration.Format == ODataFormat.Atom)
                        {
                            if (tc.ExpectedExceptionInAtom is Exception)
                            {
                                return new WriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                                {
                                    ExpectedException = (Exception)tc.ExpectedExceptionInAtom
                                };
                            }

                            var exception = testConfiguration.IsRequest ? tc.ExpectedExceptionInAtom : tc.ExpectedExceptionInAtomForResponse;
                            if (exception is ExpectedException)
                            {
                                return new WriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                                {
                                    ExpectedException2 = (ExpectedException)exception
                                };
                            }

                            return new AtomWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                            {
                                FragmentExtractor = (result) =>
                                {
                                    string typeName = (string)result.Attribute(TestAtomConstants.ODataMetadataXNamespace + TestAtomConstants.AtomTypeAttributeName);
                                    return typeName == null ? MissingTypeNameSentinelXElement : new XElement("typeName", typeName);
                                },
                                Xml = tc.XmlTypeName
                            };
                        }

                        if (testConfiguration.Format == ODataFormat.Json)
                        {
                            if (testConfiguration.Format == ODataFormat.Json)
                            {
                                if (tc.ExpectedExceptionInJsonLight is Exception)
                                {
                                    return new WriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                                    {
                                        ExpectedException = (Exception)tc.ExpectedExceptionInJsonLight
                                    };
                                }

                                var exception = testConfiguration.IsRequest ? tc.ExpectedExceptionInJsonLight : tc.ExpectedExceptionInJsonLightForResponse;

                                if (exception is ExpectedException)
                                {
                                    return new WriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                                    {
                                        ExpectedException2 = (ExpectedException)exception
                                    };
                                }
                            }

                            return new JsonWriterTestExpectedResults(this.Settings.ExpectedResultSettings)
                            {
                                FragmentExtractor = (result) =>
                                {
                                    var topLevelJsonObject = JsonLightWriterUtils.TrimWhitespace(result).Object();
                                    JsonProperty typeProperty = null;
                                    if (topLevelJsonObject != null)
                                    {
                                        typeProperty = topLevelJsonObject.Property(JsonLightConstants.ODataTypeAnnotationName);
                                    }

                                    return typeProperty == null ? MissingTypeNameSentinelJsonProperty : typeProperty.RemoveAllAnnotations(true);
                                },
                                Json = tc.JsonLightTypeName
                            };
                        }

                        throw new NotSupportedException("Format " + testConfiguration.Format.GetType().Name + " is not supported.");
                    })
                    {
                        Model = model,
                        PayloadEdmElementContainer = owningEntityType
                    };
            });

            this.CombinatorialEngineProvider.RunCombinations(
                testDescriptors,
                this.WriterTestConfigurationProvider.ExplicitFormatConfigurationsWithIndent,
                (testDescriptor, testConfig) =>
                {
                    testConfig = testConfig.Clone();
                    testConfig.MessageWriterSettings.SetServiceDocumentUri(ServiceDocumentUri);
                    testDescriptor.RunTopLevelPropertyPayload(testConfig, baselineLogger: this.Logger);
                });
        }
 public void BuildPropertyContextUriForCollectionPropertyValueWithNonNullAnnotation()
 {
     ODataCollectionValue value = new ODataCollectionValue { TypeName = "FQNS.FromObject" };
     value.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = "FQNS.FromAnnotation" });
     var contextUri = this.CreatePropertyContextUri(value);
     contextUri.OriginalString.Should().Be(BuildExpectedContextUri("#FQNS.FromAnnotation"));
 }
        /// <summary>
        /// Creates and returns an ODataCollectionValue from the given value.
        /// </summary>
        /// <param name="collectionItemType">The type of the value.</param>
        /// <param name="propertyName">If the value is a property, then it represents the name of the property. Can be null, for non-property.</param>
        /// <param name="value">The value.</param>
        /// <param name="visitedComplexTypeObjects">Set of instances of complex types encountered in the hierarchy. Used to detect cycles.</param>
        /// <param name="isDynamicProperty">Whether this collection property is a dynamic property</param>
        /// <param name="setTypeAnnotation">If true, set the type annotation on ODataValue.</param>
        /// <returns>An ODataCollectionValue representing the given value.</returns>
        internal ODataCollectionValue CreateODataCollection(Type collectionItemType, string propertyName, object value, HashSet<object> visitedComplexTypeObjects, bool isDynamicProperty, bool setTypeAnnotation = true)
        {
            Debug.Assert(collectionItemType != null, "collectionItemType != null");

            WebUtil.ValidateCollection(collectionItemType, value, propertyName, isDynamicProperty);

            PrimitiveType ptype;
            bool isCollectionOfPrimitiveTypes = PrimitiveType.TryGetPrimitiveType(collectionItemType, out ptype);

            ODataCollectionValue collection = new ODataCollectionValue();
            IEnumerable enumerablePropertyValue = (IEnumerable)value;
            string collectionItemTypeName;
            string collectionTypeName;
            if (isCollectionOfPrimitiveTypes)
            {
                collectionItemTypeName = ClientConvert.GetEdmType(Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType);

                if (enumerablePropertyValue != null)
                {                    
                    collection.Items = Util.GetEnumerable(
                        enumerablePropertyValue,
                        (val) =>
                        {
                            if (val == null)
                            {
                                return null;
                            }

                            WebUtil.ValidatePrimitiveCollectionItem(val, propertyName, collectionItemType);
                            return ConvertPrimitiveValueToRecognizedODataType(val, collectionItemType);
                        });
                }

                // TypeName for primitives should be the EDM name since that's what we will be able to look up in the model
                collectionTypeName = collectionItemTypeName;
            }
            else
            {
                Type collectionItemTypeTmp = Nullable.GetUnderlyingType(collectionItemType) ?? collectionItemType;
                bool areEnumItems = collectionItemTypeTmp.IsEnum();

                // Note that the collectionItemTypeName will be null if the context does not have the ResolveName func.
                collectionItemTypeName = this.requestInfo.ResolveNameFromType(collectionItemType);

                if (enumerablePropertyValue != null)
                {
                    collection.Items = Util.GetEnumerable(
                        enumerablePropertyValue,
                        (val) =>
                        {
                            if (areEnumItems)
                            {
                                if (val == null)
                                {
                                    return new ODataEnumValue(null, collectionItemType.FullName) as ODataValue;
                                }

                                return new ODataEnumValue(ClientTypeUtil.GetEnumValuesString(val.ToString(), collectionItemTypeTmp), collectionItemType.FullName) as ODataValue;
                            }
                            else
                            {
                                if (val == null)
                                {
                                    return null;
                                }

                                WebUtil.ValidateComplexCollectionItem(val, propertyName, collectionItemType);
                                return this.CreateODataComplexValue(val.GetType(), val, propertyName, true /*isCollectionItem*/, visitedComplexTypeObjects)
                                     as ODataValue;
                            }
                        });
                }

                // TypeName for complex types needs to be the client type name (not the one we resolved above) since it will be looked up in the client model
                collectionTypeName = collectionItemType.FullName;
            }

            // Set the type name to use for client type lookups and validation. Because setting this value can cause validation to occur, we will
            // only do it for JSON Light, in order to avoid breaking changes with the WCF Data Services 5.0 release, since it was already shipped without this.
            if (!this.requestInfo.Format.UsingAtom)
            {
                collection.TypeName = GetCollectionName(collectionTypeName);
            }

            // Ideally, we should not set type annotation on collection value. 
            // To keep backward compatibility, we'll keep it in request body, but do not include it in url.
            if (setTypeAnnotation)
            {
                string wireTypeName = GetCollectionName(collectionItemTypeName);
                collection.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = wireTypeName });
            }

            return collection;
        }