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

Cherry-pick b94324c from 8.x to 7.x: Provide async APIs for CsdlWriter and SchemaWriter (#3006) #3060

Open
wants to merge 2 commits into
base: release-7.x
Choose a base branch
from
Open
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
109 changes: 108 additions & 1 deletion src/Microsoft.OData.Core/ErrorUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.OData.Metadata;

Expand Down Expand Up @@ -56,6 +57,25 @@ internal static void WriteXmlError(XmlWriter writer, ODataError error, bool incl
WriteXmlError(writer, code, message, innerError, maxInnerErrorDepth);
}

/// <summary>
/// Asynchronously Write an error message.
/// </summary>
/// <param name="writer">The Xml writer to write to.</param>
/// <param name="error">The error instance to write.</param>
/// <param name="includeDebugInformation">A flag indicating whether error details should be written (in debug mode only) or not.</param>
/// <param name="maxInnerErrorDepth">The maximum number of nested inner errors to allow.</param>
internal static async Task WriteXmlErrorAsync(XmlWriter writer, ODataError error, bool includeDebugInformation, int maxInnerErrorDepth)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still write the XML payload?
I don't think so, we only output the JSON payload exception the CSDL, right?
If that's the case, why do we need to implement the XML related async methods?

{
Debug.Assert(writer != null, "writer != null");
Debug.Assert(error != null, "error != null");

string code, message;
ErrorUtils.GetErrorDetails(error, out code, out message);

ODataInnerError innerError = includeDebugInformation ? error.InnerError : null;
await WriteXmlErrorAsync(writer, code, message, innerError, maxInnerErrorDepth).ConfigureAwait(false);
}

/// <summary>
/// Write an error message.
/// </summary>
Expand Down Expand Up @@ -88,6 +108,38 @@ private static void WriteXmlError(XmlWriter writer, string code, string message,
writer.WriteEndElement();
}

/// <summary>
/// Asynchronously Write an error message.
/// </summary>
/// <param name="writer">The Xml writer to write to.</param>
/// <param name="code">The code of the error.</param>
/// <param name="message">The message of the error.</param>
/// <param name="innerError">Inner error details that will be included in debug mode (if present).</param>
/// <param name="maxInnerErrorDepth">The maximum number of nested inner errors to allow.</param>
private static async Task WriteXmlErrorAsync(XmlWriter writer, string code, string message, ODataInnerError innerError, int maxInnerErrorDepth)
{
Debug.Assert(writer != null, "writer != null");
Debug.Assert(code != null, "code != null");
Debug.Assert(message != null, "message != null");

// <m:error>
await writer.WriteStartElementAsync(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorElementName, ODataMetadataConstants.ODataMetadataNamespace).ConfigureAwait(false);

// <m:code>code</m:code>
await writer.WriteElementStringAsync(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorCodeElementName, ODataMetadataConstants.ODataMetadataNamespace, code).ConfigureAwait(false);

// <m:message>error message</m:message>
await writer.WriteElementStringAsync(ODataMetadataConstants.ODataMetadataNamespacePrefix, ODataMetadataConstants.ODataErrorMessageElementName, ODataMetadataConstants.ODataMetadataNamespace, message).ConfigureAwait(false);

if (innerError != null)
{
await WriteXmlInnerErrorAsync(writer, innerError, ODataMetadataConstants.ODataInnerErrorElementName, /* recursionDepth */ 0, maxInnerErrorDepth).ConfigureAwait(false);
}

// </m:error>
await writer.WriteEndElementAsync().ConfigureAwait(false);
}

/// <summary>
/// Writes the inner exception information in debug mode.
/// </summary>
Expand Down Expand Up @@ -142,5 +194,60 @@ private static void WriteXmlInnerError(XmlWriter writer, ODataInnerError innerEr
// </m:innererror> or </m:internalexception>
writer.WriteEndElement();
}

/// <summary>
/// Asynchronously Writes the inner exception information in debug mode.
/// </summary>
/// <param name="writer">The Xml writer to write to.</param>
/// <param name="innerError">The inner error to write.</param>
/// <param name="innerErrorElementName">The local name of the element representing the inner error.</param>
/// <param name="recursionDepth">The number of times this method has been called recursively.</param>
/// <param name="maxInnerErrorDepth">The maximum number of nested inner errors to allow.</param>
private static async Task WriteXmlInnerErrorAsync(XmlWriter writer, ODataInnerError innerError, string innerErrorElementName, int recursionDepth, int maxInnerErrorDepth)
{
Debug.Assert(writer != null, "writer != null");

recursionDepth++;
if (recursionDepth > maxInnerErrorDepth)
{
#if ODATA_CORE
throw new ODataException(Strings.ValidationUtils_RecursionDepthLimitReached(maxInnerErrorDepth));
#else
throw new ODataException(Microsoft.OData.Service.Strings.BadRequest_DeepRecursion(maxInnerErrorDepth));
#endif
}

// <m:innererror> or <m:internalexception>
await writer.WriteStartElementAsync(ODataMetadataConstants.ODataMetadataNamespacePrefix, innerErrorElementName, ODataMetadataConstants.ODataMetadataNamespace).ConfigureAwait(false);

//// NOTE: we add empty elements if no information is provided for the message, error type and stack trace
//// to stay compatible with Astoria.

// <m:message>...</m:message>
string errorMessage = innerError.Message ?? String.Empty;
await writer.WriteStartElementAsync(null, ODataMetadataConstants.ODataInnerErrorMessageElementName, ODataMetadataConstants.ODataMetadataNamespace).ConfigureAwait(false);
await writer.WriteStringAsync(errorMessage).ConfigureAwait(false);
await writer.WriteEndElementAsync();

// <m:type>...</m:type>
string errorType = innerError.TypeName ?? string.Empty;
await writer.WriteStartElementAsync(null, ODataMetadataConstants.ODataInnerErrorTypeElementName, ODataMetadataConstants.ODataMetadataNamespace).ConfigureAwait(false);
await writer.WriteStringAsync(errorType).ConfigureAwait(false);
await writer.WriteEndElementAsync().ConfigureAwait(false);

// <m:stacktrace>...</m:stacktrace>
string errorStackTrace = innerError.StackTrace ?? String.Empty;
await writer.WriteStartElementAsync(null, ODataMetadataConstants.ODataInnerErrorStackTraceElementName, ODataMetadataConstants.ODataMetadataNamespace).ConfigureAwait(false);
await writer.WriteStringAsync(errorStackTrace).ConfigureAwait(false);
await writer.WriteEndElementAsync().ConfigureAwait(false);

if (innerError.InnerError != null)
{
await WriteXmlInnerErrorAsync(writer, innerError.InnerError, ODataMetadataConstants.ODataInnerErrorInnerErrorElementName, recursionDepth, maxInnerErrorDepth).ConfigureAwait(false);
}

// </m:innererror> or </m:internalexception>
await writer.WriteEndElementAsync().ConfigureAwait(false);
}
}
}
}
13 changes: 13 additions & 0 deletions src/Microsoft.OData.Core/Metadata/ODataMetadataWriterUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.OData.Metadata
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
#endregion Namespaces

Expand Down Expand Up @@ -58,6 +59,18 @@ internal static void WriteError(XmlWriter writer, ODataError error, bool include
ErrorUtils.WriteXmlError(writer, error, includeDebugInformation, maxInnerErrorDepth);
}

/// <summary>
/// Asynchronously Write an error message.
/// </summary>
/// <param name="writer">The Xml writer to write to.</param>
/// <param name="error">The error instance to write.</param>
/// <param name="includeDebugInformation">A flag indicating whether error details should be written (in debug mode only) or not.</param>
/// <param name="maxInnerErrorDepth">The maximum number of nested inner errors to allow.</param>
internal static Task WriteErrorAsync(XmlWriter writer, ODataError error, bool includeDebugInformation, int maxInnerErrorDepth)
{
return ErrorUtils.WriteXmlErrorAsync(writer, error, includeDebugInformation, maxInnerErrorDepth);
}

/// <summary>
/// Creates a new XmlWriterSettings instance using the encoding.
/// </summary>
Expand Down
32 changes: 25 additions & 7 deletions src/Microsoft.OData.Core/ODataMetadataJsonOutputContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,12 @@ internal Task FlushAsync()
/// </summary>
/// <returns>A task representing the asynchronous operation of writing the metadata document.</returns>
/// <remarks>It is the responsibility of this method to flush the output before the task finishes.</remarks>
internal override Task WriteMetadataDocumentAsync()
internal override async Task WriteMetadataDocumentAsync()
{
this.AssertAsynchronous();

return TaskUtils.GetTaskForSynchronousOperationReturningTask(
() =>
{
this.WriteMetadataDocumentImplementation();
return this.FlushAsync();
});
await this.WriteMetadataDocumentImplementationAsync().ConfigureAwait(false);
await this.FlushAsync().ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -259,6 +255,28 @@ private void WriteMetadataDocumentImplementation()
}
}

private async Task WriteMetadataDocumentImplementationAsync()
{
var writerSettings = new CsdlJsonWriterSettings
{
IsIeee754Compatible = MessageWriterSettings.IsIeee754Compatible,
};

var (success, errors) = await CsdlWriter.TryWriteCsdlAsync(this.Model, this.jsonWriter, writerSettings).ConfigureAwait(false);
if (!success)
{
Debug.Assert(errors != null, "errors != null");

StringBuilder builder = new StringBuilder();
foreach (EdmError error in errors)
{
builder.AppendLine(error.ToString());
}

throw new ODataException(Strings.ODataMetadataOutputContext_ErrorWritingMetadata(builder.ToString()));
}
}

private async Task DisposeOutputStreamAsync()
{
await this.asynchronousOutputStream.FlushAsync().ConfigureAwait(false);
Expand Down
42 changes: 28 additions & 14 deletions src/Microsoft.OData.Core/ODataMetadataOutputContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,33 +100,26 @@ internal Task FlushAsync()
/// be included in the payload. This should only be used in debug scenarios.
/// </param>
/// <returns>Task which represents the pending write operation.</returns>
internal override Task WriteInStreamErrorAsync(ODataError error, bool includeDebugInformation)
internal override async Task WriteInStreamErrorAsync(ODataError error, bool includeDebugInformation)
{
this.AssertAsynchronous();

return TaskUtils.GetTaskForSynchronousOperationReturningTask(
() =>
{
ODataMetadataWriterUtils.WriteError(this.xmlWriter, error, includeDebugInformation, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth);
return this.FlushAsync();
});
await ODataMetadataWriterUtils.WriteErrorAsync(this.xmlWriter, error, includeDebugInformation, this.MessageWriterSettings.MessageQuotas.MaxNestingDepth).ConfigureAwait(false);

await this.FlushAsync().ConfigureAwait(false);
}

/// <summary>
/// Asynchronously writes a metadata document as message payload.
/// </summary>
/// <returns>A task representing the asynchronous operation of writing the metadata document.</returns>
/// <remarks>It is the responsibility of this method to flush the output before the task finishes.</remarks>
internal override Task WriteMetadataDocumentAsync()
internal override async Task WriteMetadataDocumentAsync()
{
this.AssertAsynchronous();

return TaskUtils.GetTaskForSynchronousOperationReturningTask(
() =>
{
this.WriteMetadataDocumentImplementation();
return this.FlushAsync();
});
await this.WriteMetadataDocumentImplementationAsync().ConfigureAwait(false);
await this.FlushAsync().ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -280,5 +273,26 @@ private void WriteMetadataDocumentImplementation()
throw new ODataException(Strings.ODataMetadataOutputContext_ErrorWritingMetadata(builder.ToString()));
}
}

private async Task WriteMetadataDocumentImplementationAsync()
{
Tuple<bool, IEnumerable<EdmError>> result = await CsdlWriter.TryWriteCsdlAsync(this.Model, this.xmlWriter, CsdlTarget.OData).ConfigureAwait(false);
bool success = result.Item1;

if (!success)
{
IEnumerable<EdmError> errors = result.Item2;

Debug.Assert(errors != null, "errors != null");

StringBuilder builder = new StringBuilder();
foreach (EdmError error in errors)
{
builder.AppendLine(error.ToString());
}

throw new ODataException(Strings.ODataMetadataOutputContext_ErrorWritingMetadata(builder.ToString()));
}
}
}
}
Loading