/// <summary> /// Validates that a string is either a valid absolute URI, or (if it begins with '#') it is a valid URI fragment. /// </summary> /// <param name="metadataDocumentUri">The metadata document uri.</param> /// <param name="propertyName">The property name to validate.</param> internal static void ValidateMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) { Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); string uriStringToValidate = propertyName; // If it starts with a '#', validate that the rest of the string is a valid Uri fragment. if (propertyName[0] == ODataConstants.ContextUriFragmentIndicator) { // In order to use System.Uri to validate a fragement, we first prepend the metadataDocumentUri // so that it becomes an absolute URI which we can validate with Uri.IsWellFormedUriString. uriStringToValidate = UriUtils.UriToString(metadataDocumentUri) + UriUtils.EnsureEscapedFragment(propertyName); } if (!Uri.IsWellFormedUriString(uriStringToValidate, UriKind.Absolute) || !ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) || propertyName[propertyName.Length - 1] == ODataConstants.ContextUriFragmentIndicator) { throw new ODataException(Strings.ValidationUtils_InvalidMetadataReferenceProperty(propertyName)); } if (IsOpenMetadataReferencePropertyName(metadataDocumentUri, propertyName)) { throw new ODataException(Strings.ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported(propertyName, UriUtils.UriToString(metadataDocumentUri))); } }
/// <summary> /// Reads a property name from the JSON reader and determines if it's a regular property, an instance annotation or a property annotation. /// </summary> /// <param name="propertyName">The name of the regular property which the reader is positioned on or which a property annotation belongs to.</param> /// <param name="annotationName">The name of the instance or property annotation, or null if the reader is on a regular property.</param> private void ReadPropertyName(out string propertyName, out string annotationName) { string jsonPropertyName = this.GetPropertyName(); Debug.Assert(!string.IsNullOrEmpty(jsonPropertyName), "The JSON reader guarantees that property names are not null or empty."); this.ReadInternal(); if (jsonPropertyName.StartsWith("@", StringComparison.Ordinal)) { // Instance-level annotation for the instance itself; not property name. propertyName = null; annotationName = jsonPropertyName.Substring(1); if (annotationName.IndexOf('.') == -1) { annotationName = JsonLightConstants.ODataAnnotationNamespacePrefix + annotationName; } } else { int separatorIndex = jsonPropertyName.IndexOf(JsonLightConstants.ODataPropertyAnnotationSeparatorChar); if (separatorIndex > 0) { // This is a property annotation; compute the property and annotation names propertyName = jsonPropertyName.Substring(0, separatorIndex); annotationName = jsonPropertyName.Substring(separatorIndex + 1); } else { // This is either a regular data property or an instance-level annotation int dotIndex = jsonPropertyName.IndexOf('.'); if (dotIndex < 0) { // Regular property propertyName = jsonPropertyName; annotationName = null; } else { if (ODataJsonLightUtils.IsMetadataReferenceProperty(jsonPropertyName)) { // Metadata reference property propertyName = null; annotationName = jsonPropertyName; } else { // unexpected instance annotation name throw new ODataException(Microsoft.OData.Strings.JsonReaderExtensions_UnexpectedInstanceAnnotationName(jsonPropertyName)); } } } } }
/// <summary> /// Determines if the specified property name is a name of an open metadata reference property. /// </summary> /// <param name="metadataDocumentUri">The metadata document uri.</param> /// <param name="propertyName">The property name in question.</param> /// <returns>true if the specified property name is a name of an open metadata reference property; false otherwise.</returns> internal static bool IsOpenMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName) { Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null"); Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri"); Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)"); // If a metadata reference property isn't based off of the known metadata document URI (for example, it points to a model on another server), // then it must be open. It is based off the known metadata document URI if it either is a fragment (i.e., starts with a hash) or starts with the known absolute URI. return(ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) && propertyName[0] != ODataConstants.ContextUriFragmentIndicator && !propertyName.StartsWith(UriUtils.UriToString(metadataDocumentUri), StringComparison.OrdinalIgnoreCase)); }
/// <summary> /// Gets the metadata reference fragment from the operation context uri. /// i.e. if the operation context uri is {absolute metadata document uri}#{container-qualified-operation-name}, /// this method will return #{container-qualified-operation-name}. /// </summary> /// <param name="operation">Operation in question.</param> /// <returns>The metadata reference fragment from the operation context uri.</returns> private string GetOperationMetadataString(ODataOperation operation) { Debug.Assert(operation != null && operation.Metadata != null, "operation != null && operation.Metadata != null"); string operationMetadataString = UriUtils.UriToString(operation.Metadata); Debug.Assert(ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString), "ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString)"); // If we don't have a metadata document URI (which is the case with nometadata mode), just write the string form of the Uri we were given. if (this.MetadataDocumentBaseUri == null) { return(operation.Metadata.Fragment); } Debug.Assert( !ODataJsonLightValidationUtils.IsOpenMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString), "Open metadata reference property is not supported, we should have thrown before this point."); return(ODataConstants.ContextUriFragmentIndicator + ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString)); }
/// <summary> /// Parses JSON object property starting with the current position of the JSON reader. /// </summary> /// <param name="propertyAndAnnotationCollector">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( PropertyAndAnnotationCollector propertyAndAnnotationCollector, Func <string, object> readPropertyAnnotationValue, out string parsedPropertyName) { Debug.Assert(propertyAndAnnotationCollector != null, "propertyAndAnnotationCollector != 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); // reading a nested delta resource set if (isPropertyAnnotation && String.CompareOrdinal(this.CompleteSimplifiedODataAnnotation(annotationNameFromReader), ODataAnnotationNames.ODataDelta) == 0) { // Read over the property name. this.JsonReader.Read(); parsedPropertyName = propertyNameFromReader; return(PropertyParsingResult.NestedDeltaResourceSet); } bool isInstanceAnnotation = false; if (!isPropertyAnnotation) { isInstanceAnnotation = IsInstanceAnnotation(nameFromReader); propertyNameFromReader = isInstanceAnnotation ? this.CompleteSimplifiedODataAnnotation(nameFromReader.Substring(1)) : 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(Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); } return(PropertyParsingResult.PropertyWithoutValue); } object annotationValue = null; if (isPropertyAnnotation) { annotationNameFromReader = this.CompleteSimplifiedODataAnnotation(annotationNameFromReader); // If this is a unknown odata annotation targeting a property, we skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explanation. // 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 annotationValue)) { propertyAndAnnotationCollector.AddODataPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, annotationValue); continue; } // We have another property annotation for the same property we parsed. parsedPropertyName = propertyNameFromReader; lastPropertyAnnotationNameFound = annotationNameFromReader; this.ProcessPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, propertyAndAnnotationCollector, readPropertyAnnotationValue); continue; } // If this is a unknown odata annotation, skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explanation. if (isInstanceAnnotation && this.SkippedOverUnknownODataAnnotation(propertyNameFromReader, out annotationValue)) { // collect 'odata.<unknown>' annotation: // here we know the original property name contains no '@', but '.' dot Debug.Assert(annotationNameFromReader == null, "annotationNameFromReader == null"); propertyAndAnnotationCollector.AddODataScopeAnnotation(propertyNameFromReader, annotationValue); continue; } // We are encountering the property name for the first time. // Don't read over property name, as that would cause the buffering reader to read ahead in the StartObject // state, which would break our ability to stream inline json. Instead, callers of ParseProperty will have to // call this.JsonReader.Read() as appropriate to read past the property name. parsedPropertyName = propertyNameFromReader; if (!isInstanceAnnotation && ODataJsonLightUtils.IsMetadataReferenceProperty(propertyNameFromReader)) { return(PropertyParsingResult.MetadataReferenceProperty); } if (!isInstanceAnnotation && !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"); // Handle 'odata.XXXXX' annotations if (isInstanceAnnotation && 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(Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName)); } return(PropertyParsingResult.PropertyWithoutValue); } return(PropertyParsingResult.EndOfObject); }