diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs index d1036dc..7631419 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmModelExtensions.cs @@ -162,7 +162,7 @@ namespace Microsoft.OpenApi.OData.Edm } /// - /// Check whether the operaiton is overload in the model. + /// Check whether the operation is overload in the model. /// /// The Edm model. /// The test operations. @@ -172,7 +172,7 @@ namespace Microsoft.OpenApi.OData.Edm Utils.CheckArgumentNull(model, nameof(model)); Utils.CheckArgumentNull(operation, nameof(operation)); - return model.SchemaElements.OfType() + return model.GetAllElements().OfType() .Where(o => o.IsBound == operation.IsBound && o.FullName() == operation.FullName() && o.Parameters.First().Type.Definition == operation.Parameters.First().Type.Definition ).Count() > 1; @@ -197,5 +197,28 @@ namespace Microsoft.OpenApi.OData.Edm return model.EntityContainer.OperationImports() .Where(o => o.Operation.IsBound == operationImport.Operation.IsBound && o.Name == operationImport.Name).Count() > 1; } + + /// + /// Get all of the elements in the model and its referenced models. + /// + /// All the elements. + public static IEnumerable GetAllElements(this IEdmModel model) + { + foreach (var element in model.SchemaElements.Where(el => + !ODataConstants.StandardNamespaces.Any(std => el.Namespace.StartsWith(std)))) + { + yield return element; + } + + foreach (var refModel in model.ReferencedModels) + { + foreach (var element in refModel.SchemaElements.Where(el => + !ODataConstants.StandardNamespaces.Any(std => el.Namespace.StartsWith(std)))) + { + yield return element; + } + } + } + } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmOperationProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmOperationProvider.cs index 7ebdd84..fd92847 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/EdmOperationProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/EdmOperationProvider.cs @@ -71,7 +71,7 @@ namespace Microsoft.OpenApi.OData.Edm { IDictionary> edmOperationDict = new Dictionary>(); - foreach (var edmOperation in Model.SchemaElements.OfType().Where(e => e.IsBound)) + foreach (var edmOperation in Model.GetAllElements().OfType().Where(e => e.IsBound)) { IEdmOperationParameter bindingParameter = edmOperation.Parameters.First(); diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index 4273bb7..dac4070 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -294,7 +294,7 @@ namespace Microsoft.OpenApi.OData.Edm /// private void RetrieveBoundOperationPaths() { - foreach (var edmOperation in _model.SchemaElements.OfType().Where(e => e.IsBound)) + foreach (var edmOperation in _model.GetAllElements().OfType().Where(e => e.IsBound)) { if (!CanFilter(edmOperation)) { diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs index 9788247..ffcec70 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/EdmSpatialTypeVisitor.cs @@ -4,6 +4,7 @@ // ------------------------------------------------------------ using Microsoft.OData.Edm; +using Microsoft.OpenApi.OData.Edm; namespace Microsoft.OpenApi.OData.Generator { @@ -30,7 +31,7 @@ namespace Microsoft.OpenApi.OData.Generator return; } - foreach (var element in model.SchemaElements) + foreach (var element in model.GetAllElements()) { switch (element.SchemaElementKind) { diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs index c9bb380..fb73d1a 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiExampleGenerator.cs @@ -34,7 +34,11 @@ namespace Microsoft.OpenApi.OData.Generator // Each entity type, complex type, enumeration type, and type definition directly // or indirectly used in the paths field is represented as a name / value pair of the schemas map. - foreach (var element in context.Model.SchemaElements.Where(c => !c.Namespace.StartsWith("Org.OData."))) + // Ideally this would be driven off the types used in the paths, but in practice, it is simply + // all of the types present in the model. + IEnumerable elements = context.Model.GetAllElements(); + + foreach (var element in elements) { switch (element.SchemaElementKind) { diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs index 8bffc0a..c989b15 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs @@ -38,7 +38,11 @@ namespace Microsoft.OpenApi.OData.Generator // Each entity type, complex type, enumeration type, and type definition directly // or indirectly used in the paths field is represented as a name / value pair of the schemas map. - foreach (var element in context.Model.SchemaElements.Where(c => !c.Namespace.StartsWith("Org.OData."))) + // Ideally this would be driven off the types used in the paths, but in practice, it is simply + // all of the types present in the model. + IEnumerable elements = context.Model.GetAllElements(); + + foreach (var element in elements) { switch (element.SchemaElementKind) { diff --git a/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs new file mode 100644 index 0000000..89a0d70 --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs @@ -0,0 +1,27 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.OpenApi.OData +{ + internal static class ODataConstants + { + /// + /// Namespaces used in standard included models. + /// + public static IList StandardNamespaces = new List + { + "Org.OData.", + "Edm", + "OData.Community.", + }; + + } +} diff --git a/src/OoasUtil/ComLineProcesser.cs b/src/OoasUtil/ComLineProcesser.cs index 8e5628d..02966f3 100644 --- a/src/OoasUtil/ComLineProcesser.cs +++ b/src/OoasUtil/ComLineProcesser.cs @@ -45,6 +45,11 @@ namespace OoasUtil /// public OpenApiFormat? Format { get; private set; } + /// + /// Whether KeyAsSegment is used. + /// + public bool? KeyAsSegment { get; private set; } + /// /// Output OpenApi Specification Version. /// @@ -60,6 +65,31 @@ namespace OoasUtil /// public bool IsLocalFile { get; private set; } + /// + /// Set the output to produce all derived types in responses. + /// + public bool? DerivedTypesReferencesForResponses { get; private set; } + + /// + /// Set the output to expect all derived types in request bodies. + /// + public bool? DerivedTypesReferencesForRequestBody { get; private set; } + + /// + /// Set the output to expose pagination for collections. + /// + public bool? EnablePagination { get; private set; } + + /// + /// tSet the output to use unqualified calls for bound operations. + /// + public bool? EnableUnqualifiedCall { get; private set; } + + /// + /// tDisable examples in the schema. + /// + public bool? DisableSchemaExamples { get; private set; } + /// /// Process the arguments. /// @@ -98,6 +128,14 @@ namespace OoasUtil i++; break; + case "--keyassegment": + case "-k": + if (!ProcessKeyAsSegment(true)) + { + return false; + } + break; + case "--yaml": case "-y": if (!ProcessTarget(OpenApiFormat.Yaml)) @@ -132,6 +170,46 @@ namespace OoasUtil i++; break; + case "--derivedtypesreferencesforresponses": + case "-drs": + if (!ProcessDerivedTypesReferencesForResponses(true)) + { + return false; + } + break; + + case "--derivedtypesreferencesforrequestbody": + case "-drq": + if (!ProcessDerivedTypesReferencesForRequestBody(true)) + { + return false; + } + break; + + case "--enablepagination": + case "-p": + if (!ProcessEnablePagination(true)) + { + return false; + } + break; + + case "--enableunqualifiedcall": + case "-u": + if (!ProcessEnableUnqualifiedCall(true)) + { + return false; + } + break; + + case "--disableschemaexamples": + case "-x": + if (!ProcessDisableSchemaExamples(true)) + { + return false; + } + break; + default: PrintUsage(); return false; @@ -155,6 +233,36 @@ namespace OoasUtil Version = OpenApiSpecVersion.OpenApi3_0; } + if (KeyAsSegment == null) + { + KeyAsSegment = false; + } + + if (DerivedTypesReferencesForResponses == null) + { + DerivedTypesReferencesForResponses = false; + } + + if (DerivedTypesReferencesForRequestBody == null) + { + DerivedTypesReferencesForRequestBody = false; + } + + if (EnablePagination == null) + { + EnablePagination = false; + } + + if (EnableUnqualifiedCall == null) + { + EnableUnqualifiedCall = false; + } + + if (DisableSchemaExamples == null) + { + DisableSchemaExamples = false; + } + _continue = ValidateArguments(); return _continue; } @@ -198,6 +306,84 @@ namespace OoasUtil return true; } + private bool ProcessKeyAsSegment(bool keyAsSegment) + { + if (KeyAsSegment != null) + { + Console.WriteLine("[Error:] Multiple [--keyassegment|-k] are not allowed.\n"); + PrintUsage(); + return false; + } + + KeyAsSegment = keyAsSegment; + return true; + } + + private bool ProcessDerivedTypesReferencesForResponses(bool derivedTypesReferencesForResponses) + { + if (DerivedTypesReferencesForResponses != null) + { + Console.WriteLine("[Error:] Multiple [--derivedtypesreferencesforresponses|-drs] are not allowed.\n"); + PrintUsage(); + return false; + } + + DerivedTypesReferencesForResponses = derivedTypesReferencesForResponses; + return true; + } + + private bool ProcessDerivedTypesReferencesForRequestBody(bool derivedTypesReferencesForRequestBody) + { + if (DerivedTypesReferencesForRequestBody != null) + { + Console.WriteLine("[Error:] Multiple [--derivedtypesreferencesforrequestbody|-drq] are not allowed.\n"); + PrintUsage(); + return false; + } + + DerivedTypesReferencesForRequestBody = derivedTypesReferencesForRequestBody; + return true; + } + + private bool ProcessEnablePagination(bool enablePagination) + { + if (EnablePagination != null) + { + Console.WriteLine("[Error:] Multiple [--enablepagination|-p] are not allowed.\n"); + PrintUsage(); + return false; + } + + EnablePagination = enablePagination; + return true; + } + + private bool ProcessEnableUnqualifiedCall(bool enableUnqualifiedCall) + { + if (EnableUnqualifiedCall != null) + { + Console.WriteLine("[Error:] Multiple [--enableunqualifiedcall|-u] are not allowed.\n"); + PrintUsage(); + return false; + } + + EnableUnqualifiedCall = enableUnqualifiedCall; + return true; + } + + private bool ProcessDisableSchemaExamples(bool disableSchemaExamples) + { + if (DisableSchemaExamples != null) + { + Console.WriteLine("[Error:] Multiple [--disableschemaexamples|-x] are not allowed.\n"); + PrintUsage(); + return false; + } + + DisableSchemaExamples = disableSchemaExamples; + return true; + } + private bool ProcessTarget(int version) { if (Version != null) @@ -256,6 +442,12 @@ namespace OoasUtil sb.Append(" --version|-v\t\t\tDisplay version.\n"); sb.Append(" --input|-i CsdlFileOrUrl\tSet the CSDL file name or the OData Service Url.\n"); sb.Append(" --output|-o OutputFile\tSet the output file name.\n"); + sb.Append(" --keyassegment|-k\t\t\tSet the output to use key-as-segment style URLs.\n"); + sb.Append(" --derivedtypesreferencesforresponses|-drs\t\t\tSet the output to produce all derived types in responses.\n"); + sb.Append(" --derivedtypesreferencesforrequestbody|-drq\t\t\tSet the output to expect all derived types in request bodies.\n"); + sb.Append(" --enablepagination|-p\t\t\tSet the output to expose pagination for collections.\n"); + sb.Append(" --enableunqualifiedcall|-u\t\t\tSet the output to use unqualified calls for bound operations.\n"); + sb.Append(" --disableschemaexamples|-x\t\t\tDisable examples in the schema.\n"); sb.Append(" --json|-j\t\t\tSet the output format as JSON.\n"); sb.Append(" --yaml|-y\t\t\tSet the output format as YAML.\n"); sb.Append(" --specversion|-s IntVersion\tSet the OpenApi Specification version of the output. Only 2 or 3 are supported.\n"); diff --git a/src/OoasUtil/FileOpenApiGenerator.cs b/src/OoasUtil/FileOpenApiGenerator.cs index 5ac58ee..f820a58 100644 --- a/src/OoasUtil/FileOpenApiGenerator.cs +++ b/src/OoasUtil/FileOpenApiGenerator.cs @@ -6,10 +6,13 @@ using System; using System.IO; +using System.Linq; +using System.Xml; using System.Xml.Linq; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; using Microsoft.OpenApi; +using Microsoft.OpenApi.OData; namespace OoasUtil { @@ -28,9 +31,10 @@ namespace OoasUtil /// /// The input. /// The output. - /// The output target. - public FileOpenApiGenerator(string input, string output, OpenApiFormat format, OpenApiSpecVersion version) - : base(output, format, version) + /// The format. + /// Conversion settings. + public FileOpenApiGenerator(string input, string output, OpenApiFormat format, OpenApiConvertSettings settings) + : base(output, format, settings) { Input = input; } @@ -43,7 +47,32 @@ namespace OoasUtil try { string csdl = File.ReadAllText(Input); - return CsdlReader.Parse(XElement.Parse(csdl).CreateReader()); + var directory = Path.GetDirectoryName(Input); + var parsed = XElement.Parse(csdl); + using (XmlReader mainReader = parsed.CreateReader()) + { + return CsdlReader.Parse(mainReader, u => + { + // Currently only support relative paths + if (u.IsAbsoluteUri) + { + Console.WriteLine($"Referenced model must use relative paths to the main model."); + return null; + } + + var file = Path.Combine(directory, u.OriginalString); + string referenceText = File.ReadAllText(file); + var referenceParsed = XElement.Parse(referenceText); + XmlReader referenceReader = referenceParsed.CreateReader(); + return referenceReader; + }); + } + } + catch (EdmParseException parseException) + { + Console.WriteLine("Failed to parse the CSDL file."); + Console.WriteLine(string.Join(Environment.NewLine, parseException.Errors.Select(e => e.ToString()))); + return null; } catch (Exception e) { diff --git a/src/OoasUtil/OpenApiGenerator.cs b/src/OoasUtil/OpenApiGenerator.cs index 37a08c6..fd17025 100644 --- a/src/OoasUtil/OpenApiGenerator.cs +++ b/src/OoasUtil/OpenApiGenerator.cs @@ -24,27 +24,27 @@ namespace OoasUtil /// public OpenApiFormat Format { get; } - /// - /// Output specification version. - /// - public OpenApiSpecVersion Version { get; } - /// /// Output file. /// public string Output { get; } + /// + /// Settings to use for conversion. + /// + public OpenApiConvertSettings Settings { get; } + /// /// Initializes a new instance of the class. /// - /// The input. /// The output. - /// The output target. - public OpenApiGenerator(string output, OpenApiFormat format, OpenApiSpecVersion version) + /// The output format. + /// Conversion settings. + public OpenApiGenerator(string output, OpenApiFormat format, OpenApiConvertSettings settings) { Output = output; Format = format; - Version = version; + Settings = settings; } /// @@ -56,14 +56,12 @@ namespace OoasUtil { IEdmModel edmModel = GetEdmModel(); - OpenApiConvertSettings settings = GetSettings(); - - settings.OpenApiSpecVersion = Version; + this.ModifySettings(); using (FileStream fs = File.Create(Output)) { - OpenApiDocument document = edmModel.ConvertToOpenApi(settings); - document.Serialize(fs, settings.OpenApiSpecVersion, Format); + OpenApiDocument document = edmModel.ConvertToOpenApi(Settings); + document.Serialize(fs, Settings.OpenApiSpecVersion, Format); fs.Flush(); } } @@ -76,9 +74,8 @@ namespace OoasUtil return true; } - protected virtual OpenApiConvertSettings GetSettings() + protected virtual void ModifySettings() { - return new OpenApiConvertSettings(); } protected abstract IEdmModel GetEdmModel(); diff --git a/src/OoasUtil/Program.cs b/src/OoasUtil/Program.cs index 8688c0e..eae7817 100644 --- a/src/OoasUtil/Program.cs +++ b/src/OoasUtil/Program.cs @@ -5,6 +5,8 @@ //--------------------------------------------------------------------- using System; +using System.Reflection; +using Microsoft.OpenApi.OData; namespace OoasUtil { @@ -29,13 +31,25 @@ namespace OoasUtil } OpenApiGenerator generator; + + OpenApiConvertSettings settings = new OpenApiConvertSettings + { + EnableKeyAsSegment = processer.KeyAsSegment, + EnableDerivedTypesReferencesForResponses = processer.DerivedTypesReferencesForResponses.Value, + EnableDerivedTypesReferencesForRequestBody = processer.DerivedTypesReferencesForRequestBody.Value, + EnablePagination = processer.EnablePagination.Value, + EnableUnqualifiedCall = processer.EnableUnqualifiedCall.Value, + ShowSchemaExamples = !processer.DisableSchemaExamples.Value, + OpenApiSpecVersion = processer.Version.Value, + }; + if (processer.IsLocalFile) { - generator = new FileOpenApiGenerator(processer.Input, processer.Output, processer.Format.Value, processer.Version.Value); + generator = new FileOpenApiGenerator(processer.Input, processer.Output, processer.Format.Value, settings); } else { - generator = new UrlOpenApiGenerator(new Uri(processer.Input), processer.Output, processer.Format.Value, processer.Version.Value); + generator = new UrlOpenApiGenerator(new Uri(processer.Input), processer.Output, processer.Format.Value, settings); } if (generator.Generate()) diff --git a/src/OoasUtil/README.md b/src/OoasUtil/README.md index d4904c6..b659741 100644 --- a/src/OoasUtil/README.md +++ b/src/OoasUtil/README.md @@ -25,14 +25,38 @@ Output the "JSON" format Open API document; Output the "YAML" format Open API document; -### [--specversion|-s int] +### [--keyassegment|-k] -Indicate which version, either 2 or 3, of the OpenApi specification to output. Only 2 or 3 are supported; +Output the document using key-as-segment style URLs.; + +### [--derivedtypesreferencesforresponses|-drs] + +Output the document to expect all derived types in responses.; + +### [--derivedtypesreferencesforrequestbody|-drq] + +Output the document to expect all derived types in request bodies.; + +### [--enablepagination|-p] + +Output the document to expose pagination for collections.; + +### [--enableunqualifiedcall|-u] + +Output the document to use unqualified calls for bound operations.; + +### [--disableschemaexamples|-x] + +Output the document without examples in the schema.; ### [--yaml|-y] Output the "YAML" format Open API document; +### [--specversion|-s int] + +Indicate which version, either 2 or 3, of the OpenApi specification to output. Only 2 or 3 are supported; + ### [--input|-i file] Indicate to where to get CSDL, from file or from Uri. @@ -44,7 +68,7 @@ Indicate to output file name. ## Examples -`OoasUtil.exe -j -s 3 -i http://services.odata.org/TrippinRESTierService -o trip.json` +`OoasUtil.exe -j -k -drs -drq -p -u -s 3 -i http://services.odata.org/TrippinRESTierService -o trip.json` The content of `trip.json` is similar at https://github.com/xuzhg/OData.OpenAPI/blob/master/Microsoft.OData.OpenAPI/Microsoft.OData.OpenAPI.Tests/Resources/TripService.OpenApi.json diff --git a/src/OoasUtil/UrlOpenApiGenerator.cs b/src/OoasUtil/UrlOpenApiGenerator.cs index 37ddf30..5165be0 100644 --- a/src/OoasUtil/UrlOpenApiGenerator.cs +++ b/src/OoasUtil/UrlOpenApiGenerator.cs @@ -31,9 +31,10 @@ namespace OoasUtil /// /// The input. /// The output. - /// The output target. - public UrlOpenApiGenerator(Uri input, string output, OpenApiFormat format, OpenApiSpecVersion version) - : base(output, format, version) + /// The format. + /// Conversion settings. + public UrlOpenApiGenerator(Uri input, string output, OpenApiFormat format, OpenApiConvertSettings settings) + : base(output, format, settings) { Input = input; } @@ -58,12 +59,10 @@ namespace OoasUtil return CsdlReader.Parse(XElement.Parse(csdl).CreateReader()); } - protected override OpenApiConvertSettings GetSettings() + protected override void ModifySettings() { - return new OpenApiConvertSettings - { - ServiceRoot = Input - }; + base.ModifySettings(); + Settings.ServiceRoot = Input; } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs index 411c96a..d98d45c 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Common/EdmModelHelper.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System; using System.Xml.Linq; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; @@ -12,6 +13,8 @@ using Xunit; using System.IO; using System.Xml; using System.Collections.Generic; +using System.Net; +using System.Text; using Microsoft.OData.Edm.Validation; using Xunit.Abstractions; @@ -26,6 +29,8 @@ namespace Microsoft.OpenApi.OData.Tests public static IEdmModel MultipleInheritanceEdmModel { get; } + public static IEdmModel InheritanceEdmModelAcrossReferences { get; } + public static IEdmModel BasicEdmModel { get; } public static IEdmModel MultipleSchemasEdmModel { get; } @@ -46,6 +51,7 @@ namespace Microsoft.OpenApi.OData.Tests ContractServiceModel = LoadEdmModel("Contract.OData.xml"); GraphBetaModel = LoadEdmModel("Graph.Beta.OData.xml"); MultipleSchemasEdmModel = LoadEdmModel("Multiple.Schema.OData.xml"); + InheritanceEdmModelAcrossReferences = CreateInheritanceEdmModelAcrossReferences(); } private static IEdmModel LoadEdmModel(string source) @@ -160,6 +166,50 @@ namespace Microsoft.OpenApi.OData.Tests return model; } + public static IEdmModel CreateInheritanceEdmModelAcrossReferences() + { + const string modelText = + @" + + + + + + + + + + + + + +"; + const string subModelText = + @" + + + + + + + + + + +"; + + IEdmModel model; + IEnumerable errors; + + XElement parsed = XElement.Parse(modelText); + bool result = CsdlReader.TryParse(parsed.CreateReader(), + uri => XElement.Parse(subModelText).CreateReader(), + out model, + out errors); + Assert.True(result); + return model; + } + private static IEdmModel CreateEdmModel() { var model = new EdmModel(); @@ -359,12 +409,12 @@ namespace Microsoft.OpenApi.OData.Tests public static string GetCsdl(IEdmModel model) { - string edmx = string.Empty; + string edmx = String.Empty; using (StringWriter sw = new StringWriter()) { XmlWriterSettings settings = new XmlWriterSettings(); - settings.Encoding = System.Text.Encoding.UTF8; + settings.Encoding = Encoding.UTF8; settings.Indent = true; using (XmlWriter xw = XmlWriter.Create(sw, settings)) diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmModelExtensionsTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmModelExtensionsTests.cs index b85b702..a9e919c 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmModelExtensionsTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/EdmModelExtensionsTests.cs @@ -3,8 +3,12 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using System.Collections.Generic; using System.Linq; +using System.Xml.Linq; using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Validation; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -55,5 +59,21 @@ namespace Microsoft.OpenApi.OData.Edm.Tests Assert.Equal(2, baseTypes.Count()); Assert.Equal(new[] { subEntityType, baseEntityType }, baseTypes); } + + [Fact] + public void GetAllElementsReturnsElementsFromAllModels() + { + // Arrange + IEdmModel model = EdmModelHelper.InheritanceEdmModelAcrossReferences; + + // Act + var elements = model.GetAllElements(); + + // Assert + Assert.Collection(elements, + e => Assert.Equal("Customer", e.Name), + e => Assert.Equal("Default", e.Name), + e => Assert.Equal("CustomerBase", e.Name)); + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs index 4d975d8..331d23e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs @@ -335,6 +335,52 @@ namespace Microsoft.OpenApi.OData.Tests }" .ChangeLineBreaks(), json); } + + [Fact] + public void CreateEntityTypeWithCrossReferenceBaseSchemaReturnCorrectSchema() + { + // Arrange + IEdmModel model = EdmModelHelper.InheritanceEdmModelAcrossReferences; + ODataContext context = new ODataContext(model, new OpenApiConvertSettings + { + ShowSchemaExamples = true + }); + IEdmEntityType entity = model.SchemaElements.OfType().First(t => t.Name == "Customer"); + Assert.NotNull(entity); // Guard + + // Act + var schema = context.CreateStructuredTypeSchema(entity); + + // Assert + Assert.NotNull(schema); + Assert.True(String.IsNullOrEmpty(schema.Type)); + + Assert.NotNull(schema.AllOf); + Assert.Null(schema.AnyOf); + Assert.Null(schema.OneOf); + Assert.Null(schema.Properties); + + Assert.Equal(2, schema.AllOf.Count); + var baseSchema = schema.AllOf.First(); + Assert.NotNull(baseSchema.Reference); + Assert.Equal(ReferenceType.Schema, baseSchema.Reference.Type); + Assert.Equal("SubNS.CustomerBase", baseSchema.Reference.Id); + + var declaredSchema = schema.AllOf.Last(); + Assert.Equal("object", declaredSchema.Type); + Assert.Null(declaredSchema.AllOf); + Assert.Null(declaredSchema.AnyOf); + Assert.Null(declaredSchema.OneOf); + + Assert.NotNull(declaredSchema.Properties); + Assert.Equal(1, declaredSchema.Properties.Count); + var property = Assert.Single(declaredSchema.Properties); + Assert.Equal("Extra", property.Key); + Assert.Equal("integer", property.Value.Type); + Assert.Null(property.Value.OneOf); + + Assert.Equal("Customer", declaredSchema.Title); + } #endregion #region EnumTypeSchema diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs index 9534afc..a12a536 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/EntityPathItemHandlerTests.cs @@ -9,6 +9,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Properties; +using Microsoft.OpenApi.OData.Tests; using Xunit; namespace Microsoft.OpenApi.OData.PathItem.Tests @@ -75,6 +76,31 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests pathItem.Operations.Select(o => o.Key)); } + [Fact] + public void CreateEntityPathItemReturnsCorrectPathItemWithReferences() + { + // Test that references don't disturb the paths. + + // Arrange + IEdmModel model = EdmModelHelper.InheritanceEdmModelAcrossReferences; + ODataContext context = new ODataContext(model); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); + Assert.NotNull(entitySet); // guard + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType())); + + // Act + var pathItem = _pathItemHandler.CreatePathItem(context, path); + + // Assert + Assert.NotNull(pathItem); + + Assert.NotNull(pathItem.Operations); + Assert.NotEmpty(pathItem.Operations); + Assert.Equal(3, pathItem.Operations.Count); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete }, + pathItem.Operations.Select(o => o.Key)); + } + [Theory] [InlineData(true, new OperationType[] { OperationType.Get, OperationType.Patch, OperationType.Delete })] [InlineData(false, new OperationType[] { OperationType.Patch, OperationType.Delete })]