/// <summary> /// Reads and validates a string value from the json reader. /// </summary> /// <param name="annotationName">The name of the annotation being read (used for error reporting).</param> /// <returns>The string that was read.</returns> internal string ReadAndValidateAnnotationStringValue(string annotationName) { DebugUtils.CheckNoExternalCallers(); string valueRead = this.JsonReader.ReadStringValue(annotationName); ODataJsonLightReaderUtils.ValidateAnnotationStringValue(valueRead, annotationName); return(valueRead); }
/// <summary> /// Reads and validates a string value from the json reader and processes it as a long. /// </summary> /// <param name="annotationName">The name of the annotation being read (used for error reporting).</param> /// <returns>The long that was read.</returns> internal long ReadAndValidateAnnotationStringValueAsLong(string annotationName) { DebugUtils.CheckNoExternalCallers(); string stringValue = this.ReadAndValidateAnnotationStringValue(annotationName); return((long)ODataJsonLightReaderUtils.ConvertValue( stringValue, EdmCoreModel.Instance.GetInt64(false), this.MessageReaderSettings, this.Version, /*validateNullValue*/ true, annotationName)); }
/// <summary> /// Reads a property value which occurs in the "odata.error" object scope. /// </summary> /// <param name="error">The <see cref="ODataError"/> object to update with the data from this property value.</param> /// <param name="propertyName">The name of the property whose value is to be read.</param> /// <param name="duplicationPropertyNameChecker">DuplicatePropertyNamesChecker to use for extracting property annotations /// targetting any custom instance annotations on the error.</param> /// <remarks> /// Pre-Condition: any - The value of the property being read. /// Post-Condition: JsonNodeType.Property - The property after the one being read. /// JsonNodeType.EndObject - The end of the "odata.error" object. /// any - Anything else after the property value is an invalid payload (but won't fail in this method). /// </remarks> private void ReadPropertyValueInODataErrorObject(ODataError error, string propertyName, DuplicatePropertyNamesChecker duplicationPropertyNameChecker) { switch (propertyName) { case JsonConstants.ODataErrorCodeName: error.ErrorCode = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorCodeName); break; case JsonConstants.ODataErrorMessageName: this.ReadErrorMessageObject(error); break; case JsonConstants.ODataErrorInnerErrorName: error.InnerError = this.ReadInnerError(0 /* recursionDepth */); break; default: // See if it's an instance annotation if (ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName)) { ODataJsonLightPropertyAndValueDeserializer valueDeserializer = new ODataJsonLightPropertyAndValueDeserializer(this.JsonLightInputContext); object typeName = null; var odataAnnotations = duplicationPropertyNameChecker.GetODataPropertyAnnotations(propertyName); if (odataAnnotations != null) { odataAnnotations.TryGetValue(ODataAnnotationNames.ODataType, out typeName); } var value = valueDeserializer.ReadNonEntityValue( typeName as string, null /*expectedValueTypeReference*/, null /*duplicatePropertiesNamesChecker*/, null /*collectionValidator*/, false /*validateNullValue*/, false /*isTopLevelPropertyValue*/, false /*insideComplexValue*/, propertyName, true); error.AddInstanceAnnotationForReading(propertyName, value); } else { // we only allow a 'code', 'message' and 'innererror' properties in the value of the 'odata.error' property or custom instance annotations throw new ODataException(Strings.ODataJsonLightErrorDeserializer_TopLevelErrorValueWithInvalidProperty(propertyName)); } break; } }
/// <summary> /// Process the current property annotation. /// </summary> /// <param name="annotatedPropertyName">The name being annotated. Can be a property or an instance annotation.</param> /// <param name="annotationName">The annotation targeting the <paramref name="annotatedPropertyName"/>.</param> /// <param name="duplicatePropertyNamesChecker">The duplicate property names checker.</param> /// <param name="readPropertyAnnotationValue">Callback to read the property annotation value.</param> private void ProcessPropertyAnnotation(string annotatedPropertyName, string annotationName, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, Func <string, object> readPropertyAnnotationValue) { // We don't currently support annotation targeting an instance annotation except for the @odata.type property annotation. if (ODataJsonLightReaderUtils.IsAnnotationProperty(annotatedPropertyName) && string.CompareOrdinal(annotationName, ODataAnnotationNames.ODataType) != 0) { throw new ODataException(OData.Strings.ODataJsonLightDeserializer_OnlyODataTypeAnnotationCanTargetInstanceAnnotation(annotationName, annotatedPropertyName, ODataAnnotationNames.ODataType)); } // Read over the property name. this.JsonReader.Read(); if (ODataJsonLightReaderUtils.IsODataAnnotationName(annotationName)) { // OData annotations are read. duplicatePropertyNamesChecker.AddODataPropertyAnnotation(annotatedPropertyName, annotationName, readPropertyAnnotationValue(annotationName)); } else { // All other property annotations are ignored. duplicatePropertyNamesChecker.AddCustomPropertyAnnotation(annotatedPropertyName, annotationName); this.JsonReader.SkipValue(); } }
/// <summary> /// Reads a property value which occurs in the "message" object scope. /// </summary> /// <param name="error">The <see cref="ODataError"/> object to update with the data from this property value.</param> /// <param name="propertyName">The name of the propety whose value is to be read.</param> /// <remarks> /// Pre-Condition: any - The value of the property being read. /// Post-Condition: JsonNodeType.Property - The property after the one being read. /// JsonNodeType.EndObject - The end of the "message" object. /// any - Anything else after the property value is an invalid payload (but won't fail in this method). /// </remarks> private void ReadPropertyValueInMessageObject(ODataError error, string propertyName) { switch (propertyName) { case JsonConstants.ODataErrorMessageLanguageName: error.MessageLanguage = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorMessageLanguageName); break; case JsonConstants.ODataErrorMessageValueName: error.Message = this.JsonReader.ReadStringValue(JsonConstants.ODataErrorMessageValueName); break; default: if (ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName)) { // ignore custom instance annotations this.JsonReader.SkipValue(); break; } // we only allow a 'lang' and 'value' properties in the value of the 'message' property throw new ODataException(Strings.ODataJsonErrorDeserializer_TopLevelErrorMessageValueWithInvalidProperty(propertyName)); } }
/// <summary> /// Detects the payload kind(s). /// </summary> /// <param name="detectionInfo">Additional information available for the payload kind detection.</param> /// <returns>An enumerable of zero, one or more payload kinds that were detected from looking at the payload in the message stream.</returns> private IEnumerable <ODataPayloadKind> DetectPayloadKindImplementation(ODataPayloadKindDetectionInfo detectionInfo) { Debug.Assert(detectionInfo != null, "detectionInfo != null"); Debug.Assert(this.JsonReader.DisableInStreamErrorDetection, "The in-stream error detection should be disabled for payload kind detection."); this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); // If we found a metadata URI and parsed it, look at the detected payload kind and return it. if (this.MetadataUriParseResult != null) { // Store the parsed metadata URI on the input context so we can avoid parsing it again. detectionInfo.SetPayloadKindDetectionFormatState(new ODataJsonLightPayloadKindDetectionState(this.MetadataUriParseResult)); return(this.MetadataUriParseResult.DetectedPayloadKinds); } // Otherwise this is a payload without metadata URI and we have to start sniffing; only odata.error payloads // don't have a metadata URI so check for a single 'odata.error' property (ignoring custom annotations). ODataError error = null; while (this.JsonReader.NodeType == JsonNodeType.Property) { string propertyName = this.JsonReader.ReadPropertyName(); if (!ODataJsonLightReaderUtils.IsAnnotationProperty(propertyName)) { // If we find a non-annotation property, this is not an error payload return(Enumerable.Empty <ODataPayloadKind>()); } string annotatedPropertyName, annotationName; if (!ODataJsonLightDeserializer.TryParsePropertyAnnotation(propertyName, out annotatedPropertyName, out annotationName)) { // Instance annotation; check for odata.error if (ODataJsonLightReaderUtils.IsODataAnnotationName(propertyName)) { if (string.CompareOrdinal(ODataAnnotationNames.ODataError, propertyName) == 0) { // If we find multiple errors or an invalid error value, this is not an error payload. if (error != null || !this.JsonReader.StartBufferingAndTryToReadInStreamErrorPropertyValue(out error)) { return(Enumerable.Empty <ODataPayloadKind>()); } // At this point we successfully read the first odata.error property. // Skip the error value and check whether there are more properties. this.JsonReader.SkipValue(); } else { // Any odata.* instance annotations other than odata.error are not allowed for errors. return(Enumerable.Empty <ODataPayloadKind>()); } } else { // Skip custom instance annotations this.JsonReader.SkipValue(); } } else { // Property annotation; not allowed for errors return(Enumerable.Empty <ODataPayloadKind>()); } } // If we got here without finding a metadata URI or an error payload, we don't know what this is. if (error == null) { return(Enumerable.Empty <ODataPayloadKind>()); } return(new ODataPayloadKind[] { ODataPayloadKind.Error }); }
/// <summary> /// Writes an annotation group declaration or annotation group reference if specified for the entry. /// </summary> /// <param name="entry">The entry to write the annotation group declaration or reference for.</param> internal void WriteAnnotationGroup(ODataEntry entry) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entry != null, "entry != null"); ODataJsonLightAnnotationGroup annotationGroup = entry.GetAnnotation <ODataJsonLightAnnotationGroup>(); if (annotationGroup == null) { return; } if (!this.JsonLightOutputContext.WritingResponse) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_AnnotationGroupInRequest); } string annotationGroupName = annotationGroup.Name; if (string.IsNullOrEmpty(annotationGroupName)) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_AnnotationGroupWithoutName); } // Check whether this is the first occurrence of the annotation group. ODataJsonLightAnnotationGroup existingAnnotationGroup; if (this.annotationGroups.TryGetValue(annotationGroupName, out existingAnnotationGroup)) { // Make sure the annotation groups are reference equal if they have the same name. if (!object.ReferenceEquals(existingAnnotationGroup, annotationGroup)) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_DuplicateAnnotationGroup(annotationGroupName)); } // Write an annotation group reference this.JsonWriter.WriteName(ODataAnnotationNames.ODataAnnotationGroupReference); this.JsonWriter.WritePrimitiveValue(annotationGroupName, this.JsonLightOutputContext.Version); } else { // Write an annotation group declaration this.JsonWriter.WriteName(ODataAnnotationNames.ODataAnnotationGroup); this.JsonWriter.StartObjectScope(); this.JsonWriter.WriteName(JsonLightConstants.ODataAnnotationGroupNamePropertyName); this.JsonWriter.WritePrimitiveValue(annotationGroupName, this.JsonLightOutputContext.Version); if (annotationGroup.Annotations != null) { foreach (KeyValuePair <string, object> kvp in annotationGroup.Annotations) { string annotationKey = kvp.Key; Debug.Assert(annotationKey != null, "annotationKey != null"); if (annotationKey.Length == 0) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_AnnotationGroupMemberWithoutName(annotationGroup.Name)); } if (!ODataJsonLightReaderUtils.IsAnnotationProperty(annotationKey)) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_AnnotationGroupMemberMustBeAnnotation(annotationKey, annotationGroup.Name)); } this.JsonWriter.WriteName(annotationKey); object annotationValue = kvp.Value; string annotationValueString = annotationValue as string; if (annotationValueString == null && annotationValue != null) { throw new ODataException(OData.Strings.ODataJsonLightEntryAndFeedSerializer_AnnotationGroupMemberWithInvalidValue(annotationKey, annotationGroup.Name, annotationValue.GetType().FullName)); } this.JsonWriter.WritePrimitiveValue(annotationValueString, this.JsonLightOutputContext.Version); } } this.JsonWriter.EndObjectScope(); // Remember that we wrote the declaration of the annotation group. this.annotationGroups.Add(annotationGroupName, annotationGroup); } }
/// <summary> /// Parses JSON object property starting with the current position of the JSON reader. /// </summary> /// <param name="duplicatePropertyNamesChecker">The duplicate property names checker to use, it will also store the property annotations found.</param> /// <param name="readPropertyAnnotationValue">Function called to read property annotation value.</param> /// <param name="parsedPropertyName">The name of the property or instance annotation found.</param> /// <returns> /// PropertyWithValue - a property with value was found. The <paramref name="parsedPropertyName"/> contains the name of the property. /// The reader is positioned on the property value. /// PropertyWithoutValue - a property without a value was found. The <paramref name="parsedPropertyName"/> contains the name of the property. /// The reader is positioned on the node after property annotations (so either a property or end of object). /// ODataInstanceAnnotation - an odata instance annotation was found. The <paramref name="parsedPropertyName"/> contains the name of the annotation. /// The reader is positioned on the value of the annotation. /// CustomInstanceAnnotation - a custom instance annotation was found. The <paramref name="parsedPropertyName"/> contains the name of the annotation. /// The reader is positioned on the value of the annotation. /// MetadataReferenceProperty - a property which is a reference into the metadata was found. /// The reader is positioned on the value of the property. /// EndOfObject - end of the object scope was reached and no properties are to be reported. The <paramref name="parsedPropertyName"/> is null. /// This can only happen if there's a property annotation which is ignored (for example custom one) at the end of the object. /// </returns> private PropertyParsingResult ParseProperty( DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, Func <string, object> readPropertyAnnotationValue, out string parsedPropertyName) { Debug.Assert(duplicatePropertyNamesChecker != null, "duplicatePropertyNamesChecker != null"); Debug.Assert(readPropertyAnnotationValue != null, "readPropertyAnnotationValue != null"); string lastPropertyAnnotationNameFound = null; parsedPropertyName = null; while (this.JsonReader.NodeType == JsonNodeType.Property) { string nameFromReader = this.JsonReader.GetPropertyName(); string propertyNameFromReader, annotationNameFromReader; bool isPropertyAnnotation = TryParsePropertyAnnotation(nameFromReader, out propertyNameFromReader, out annotationNameFromReader); propertyNameFromReader = propertyNameFromReader ?? nameFromReader; // If parsedPropertyName is set and is different from the property name the reader is currently on, // we have parsed a property annotation for a different property than the one at the current position. if (parsedPropertyName != null && string.CompareOrdinal(parsedPropertyName, propertyNameFromReader) != 0) { if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName)) { throw new ODataException(OData.Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); } return(PropertyParsingResult.PropertyWithoutValue); } duplicatePropertyNamesChecker.AnnotationCollector.ShouldCollectAnnotation = (this.MessageReaderSettings.UndeclaredPropertyBehaviorKinds == ODataUndeclaredPropertyBehaviorKinds.SupportUndeclaredValueProperty); string skippedRawJson = null; if (isPropertyAnnotation) { duplicatePropertyNamesChecker.AnnotationCollector.TryPeekAndCollectAnnotationRawJson( this.JsonReader, propertyNameFromReader, annotationNameFromReader); // If this is a unknown odata annotation targeting a property, we skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination. // Note that we don't skip over unknown odata annotations targeting another annotation. We don't allow annotations (except odata.type) targeting other annotations, // so this.ProcessPropertyAnnotation() will test and fail for that case. if (!ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader) && this.SkippedOverUnknownODataAnnotation(annotationNameFromReader, out skippedRawJson)) { continue; } // We have another property annotation for the same property we parsed. parsedPropertyName = propertyNameFromReader; lastPropertyAnnotationNameFound = annotationNameFromReader; this.ProcessPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, duplicatePropertyNamesChecker, readPropertyAnnotationValue); continue; } // If this is a unknown odata annotation, skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination. if (this.SkippedOverUnknownODataAnnotation(propertyNameFromReader, out skippedRawJson)) { // collect 'odata.<unknown>' annotation: // here we know the original property name contains no '@', but '.' dot Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); duplicatePropertyNamesChecker.AnnotationCollector.TryAddPropertyAnnotationRawJson( "", propertyNameFromReader, skippedRawJson); continue; } // We are encountering the property name for the first time. // Read over the property name. this.JsonReader.Read(); parsedPropertyName = propertyNameFromReader; if (ODataJsonLightUtils.IsMetadataReferenceProperty(propertyNameFromReader)) { return(PropertyParsingResult.MetadataReferenceProperty); } if (!ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader)) { // Normal property return(PropertyParsingResult.PropertyWithValue); } // collect 'xxx.yyyy' annotation: // here we know the original property name contains no '@', but '.' dot Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); duplicatePropertyNamesChecker.AnnotationCollector.TryPeekAndCollectAnnotationRawJson( this.JsonReader, "", propertyNameFromReader); // propertyNameFromReader is the annotation name // Handle 'odata.XXXXX' annotations if (ODataJsonLightReaderUtils.IsODataAnnotationName(propertyNameFromReader)) { return(PropertyParsingResult.ODataInstanceAnnotation); } // Handle custom annotations return(PropertyParsingResult.CustomInstanceAnnotation); } this.AssertJsonCondition(JsonNodeType.EndObject); if (parsedPropertyName != null) { if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName)) { throw new ODataException(OData.Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); } return(PropertyParsingResult.PropertyWithoutValue); } return(PropertyParsingResult.EndOfObject); }