Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support reading relative context urls with bulk updates #2618

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,55 @@ internal static ODataJsonLightContextUriParseResult Parse(
ODataPayloadKind payloadKind,
Func<IEdmType, string, IEdmType> clientCustomTypeResolver,
bool needParseFragment,
bool throwIfMetadataConflict = true)
bool throwIfMetadataConflict = true,
Uri baseUri = null,
IEdmNavigationSource navigationSource = null)
{
if (contextUriFromPayload == null)
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_NullMetadataDocumentUri);
}

// Create an absolute URI from the payload string
// TODO: Support relative context uri and resolving other relative uris
// Create a context URI from the payload string.
Uri contextUri;
if (!Uri.TryCreate(contextUriFromPayload, UriKind.Absolute, out contextUri))
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute(contextUriFromPayload));
if (baseUri == null)
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(contextUriFromPayload));
}
else
{
ODataUri oDataUri = new ODataUri() { ServiceRoot = baseUri };

// This caters for this format: #$delta.
if (contextUriFromPayload == ODataConstants.HashDeltaResourceSet)
{
if (string.IsNullOrEmpty(navigationSource?.Name))
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(contextUriFromPayload));
}
else
{
contextUriFromPayload = oDataUri.MetadataDocumentUri.ToString() +
ODataConstants.ContextUriFragmentIndicator +
navigationSource.Name +
ODataConstants.UriSegmentSeparator +
ODataConstants.DeltaResourceSet;
}
}
else
{
contextUriFromPayload = contextUriFromPayload.StartsWith(ODataConstants.UriMetadataSegmentHash, StringComparison.Ordinal)
? baseUri + contextUriFromPayload
: oDataUri.MetadataDocumentUri.ToString() + ODataConstants.ContextUriFragmentIndicator + contextUriFromPayload;
}

if (!Uri.TryCreate(baseUri, contextUriFromPayload, out contextUri))
{
throw new ODataException(ODataErrorStrings.ODataJsonLightContextUriParser_InvalidContextUrl(contextUriFromPayload));
}
}
}

ODataJsonLightContextUriParser parser = new ODataJsonLightContextUriParser(model, contextUri);
Expand Down
14 changes: 10 additions & 4 deletions src/Microsoft.OData.Core/JsonLight/ODataJsonLightDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ internal void ReadPayloadStart(
ODataPayloadKind payloadKind,
PropertyAndAnnotationCollector propertyAndAnnotationCollector,
bool isReadingNestedPayload,
bool allowEmptyPayload)
bool allowEmptyPayload,
IEdmNavigationSource navigationSource = null)
{
this.JsonReader.AssertNotBuffering();
Debug.Assert(isReadingNestedPayload || this.JsonReader.NodeType == JsonNodeType.None, "Pre-Condition: JSON reader must not have been used yet when not reading a nested payload.");
Expand Down Expand Up @@ -298,7 +299,9 @@ internal void ReadPayloadStart(
payloadKind,
this.MessageReaderSettings.ClientCustomTypeResolver,
this.JsonLightInputContext.ReadingResponse || payloadKind == ODataPayloadKind.Delta,
this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata);
this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata,
this.BaseUri,
navigationSource);
}

this.contextUriParseResult = parseResult;
Expand All @@ -325,7 +328,8 @@ internal async Task ReadPayloadStartAsync(
ODataPayloadKind payloadKind,
PropertyAndAnnotationCollector propertyAndAnnotationCollector,
bool isReadingNestedPayload,
bool allowEmptyPayload)
bool allowEmptyPayload,
IEdmNavigationSource navigationSource = null)
{
this.JsonReader.AssertNotBuffering();
Debug.Assert(
Expand All @@ -349,7 +353,9 @@ internal async Task ReadPayloadStartAsync(
payloadKind,
this.MessageReaderSettings.ClientCustomTypeResolver,
this.JsonLightInputContext.ReadingResponse || payloadKind == ODataPayloadKind.Delta,
this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata);
this.JsonLightInputContext.MessageReaderSettings.ThrowIfTypeConflictsWithMetadata,
this.BaseUri,
navigationSource);
}

#if DEBUG
Expand Down
19 changes: 15 additions & 4 deletions src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ await this.jsonLightResourceDeserializer.ReadPayloadStartAsync(
payloadKind,
propertyAndAnnotationCollector,
this.IsReadingNestedPayload,
allowEmptyPayload: false).ConfigureAwait(false);
allowEmptyPayload: false,
this.CurrentNavigationSource).ConfigureAwait(false);

ResolveScopeInfoFromContextUrl();

Expand Down Expand Up @@ -1231,7 +1232,10 @@ private ODataUri ResolveODataUriFromContextUrl(ODataNestedResourceInfo nestedInf
UriUtils.UriToString(nestedInfo.ContextUrl),
payloadKind,
this.jsonLightResourceDeserializer.MessageReaderSettings.ClientCustomTypeResolver,
this.jsonLightResourceDeserializer.JsonLightInputContext.ReadingResponse).Path;
this.jsonLightResourceDeserializer.JsonLightInputContext.ReadingResponse,
true,
this.jsonLightResourceDeserializer.MessageReaderSettings.BaseUri,
this.CurrentNavigationSource).Path;

return new ODataUri() { Path = odataPath };
}
Expand Down Expand Up @@ -1931,7 +1935,11 @@ private void ReadResourceSetItemStart(PropertyAndAnnotationCollector propertyAnd
contextUriStr,
this.ReadingDelta ? ODataPayloadKind.Delta : ODataPayloadKind.Resource,
this.jsonLightResourceDeserializer.MessageReaderSettings.ClientCustomTypeResolver,
this.jsonLightInputContext.ReadingResponse || this.ReadingDelta);
this.jsonLightInputContext.ReadingResponse || this.ReadingDelta,
true,
this.jsonLightResourceDeserializer.BaseUri,
this.CurrentNavigationSource);

if (parseResult != null)
{
// a top-level (deleted) resource in a delta response can come from any entity set
Expand Down Expand Up @@ -3459,7 +3467,10 @@ await this.jsonLightResourceDeserializer.JsonReader.ReadAsync()
contextUriFromPayload: contextUriStr,
payloadKind: this.ReadingDelta ? ODataPayloadKind.Delta : ODataPayloadKind.Resource,
clientCustomTypeResolver: this.jsonLightResourceDeserializer.MessageReaderSettings.ClientCustomTypeResolver,
needParseFragment: this.jsonLightInputContext.ReadingResponse || this.ReadingDelta);
needParseFragment: this.jsonLightInputContext.ReadingResponse || this.ReadingDelta,
true,
this.jsonLightResourceDeserializer.BaseUri,
this.CurrentNavigationSource);

if (parseResult != null)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Microsoft.OData.Core/ODataConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ internal static class ODataInternalConstants
/// <summary>A segment name in a URI that indicates metadata is being requested.</summary>
internal const string UriMetadataSegment = "$metadata";

/// <summary>A segment name in a URI that indicates metadata is being requested and that has a # suffix. Exa. $metadata#.</summary>
internal const string UriMetadataSegmentHash = UriMetadataSegment + TypeNamePrefix;

/// <summary>The OData prefix</summary>
internal const string ODataPrefix = "odata";

Expand Down Expand Up @@ -191,6 +194,9 @@ internal static class ODataInternalConstants
/// <summary>The $delta token indicates delta resource set.</summary>
internal const string ContextUriDeltaResourceSet = UriSegmentSeparator + DeltaResourceSet;

/// <summary>The $delta token indicates delta resource set.</summary>
internal const string HashDeltaResourceSet = TypeNamePrefix + DeltaResourceSet;

/// <summary>The $deletedEntity token indicates deleted resource.</summary>
internal const string DeletedEntry = "$deletedEntity";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,29 @@ private EdmModel GetModel()
return model;
}

// TODO: Support relative context uri and resolving other relative uris
[Fact]
public void ParseRelativeContextUrlShouldThrowException()
public void ParseRelativeContextUrlShouldNotThrowException()
ElizabethOkerio marked this conversation as resolved.
Show resolved Hide resolved
{
string relativeUrl = "$metadata#R";
Action parseContextUri = () => ODataJsonLightContextUriParser.Parse(new EdmModel(), relativeUrl, ODataPayloadKind.Unsupported, null, true);
parseContextUri.Throws<ODataException>(ErrorStrings.ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute(relativeUrl));
string relativeUrl = "$metadata#People";
var parsedContextUrl = ODataJsonLightContextUriParser.Parse(this.GetModel(), relativeUrl, ODataPayloadKind.Unsupported, null, true, true, new Uri("https://www.example.com/api/"));
Assert.Equal(new Uri("https://www.example.com/api/$metadata#People"), parsedContextUrl.ContextUri);
}

[Fact]
public void ParseRelativeContextUrlWithoutMetadataShouldNotThrowException()
{
string relativeUrl = "People";
var parsedContextUrl = ODataJsonLightContextUriParser.Parse(this.GetModel(), relativeUrl, ODataPayloadKind.Unsupported, null, true, true, new Uri("https://www.example.com/api/"));
Assert.Equal(new Uri("https://www.example.com/api/$metadata#People"), parsedContextUrl.ContextUri);
}

[Fact]
public void ParseRelativeContextUrlWithOnlyDeltaSegmentShouldNotThrowException()
{
string relativeUrl = "#$delta";
IEdmNavigationSource navigationSource = this.GetModel().FindDeclaredEntitySet("People") as IEdmNavigationSource;
var parsedContextUrl = ODataJsonLightContextUriParser.Parse(this.GetModel(), relativeUrl, ODataPayloadKind.Unsupported, null, true, true, new Uri("https://www.example.com/api/"), navigationSource);
Assert.Equal(new Uri("https://www.example.com/api/$metadata#People"), parsedContextUrl.ContextUri);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,17 @@ public void JsonLightContextUriParserErrorTest()
},
new ContextUriParserTestCase
{
DebugDescription = "empty string is a relative URI; context URIs have to absolute",
DebugDescription = "empty string is a relative URI; relative context URLs should not be null.",
ContextUri = string.Empty,
Model = this.testModel,
ExpectedException = ODataExpectedExceptions.ODataException("ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute", "")
ExpectedException = ODataExpectedExceptions.ODataException("ODataJsonLightContextUriParser_InvalidContextUrl")
},
new ContextUriParserTestCase
{
DebugDescription = "another relative URI; context URIs have to absolute",
DebugDescription = "another relative URI; context URIs should have null baseUri",
ContextUri = "relativeUri",
Model = this.testModel,
ExpectedException = ODataExpectedExceptions.ODataException("ODataJsonLightContextUriParser_TopLevelContextUrlShouldBeAbsolute", "relativeUri")
ExpectedException = ODataExpectedExceptions.ODataException("ODataJsonLightContextUriParser_InvalidContextUrl")
},
new ContextUriParserTestCase
{
Expand Down