Skip to content

Commit

Permalink
fix: adds generic shallow copy method to avoid inadvertent conversion…
Browse files Browse the repository at this point in the history
…s of references to schemas

Signed-off-by: Vincent Biret <[email protected]>
  • Loading branch information
baywet committed Jan 29, 2025
1 parent e3c80a3 commit e4c14a4
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 79 deletions.
12 changes: 12 additions & 0 deletions src/Microsoft.OpenApi/Interfaces/IShallowCopyable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Microsoft.OpenApi.Interfaces;
/// <summary>
/// Interface for shallow copyable objects.
/// </summary>
/// <typeparam name="T">The type of the resulting object</typeparam>
public interface IShallowCopyable<out T>
{
/// <summary>
/// Create a shallow copy of the current instance.
/// </summary>
T CreateShallowCopy();
}
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.OpenApi.Models.Interfaces;
/// Defines the base properties for the schema object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
public interface IOpenApiSchema : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible, IShallowCopyable<IOpenApiSchema>
{

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public OpenApiHeader(IOpenApiHeader header)
Style = header?.Style ?? Style;
Explode = header?.Explode ?? Explode;
AllowReserved = header?.AllowReserved ?? AllowReserved;
Schema = header?.Schema != null ? new OpenApiSchema(header.Schema) : null;
Schema = header?.Schema?.CreateShallowCopy();
Example = header?.Example != null ? JsonNodeCloneHelper.Clone(header.Example) : null;
Examples = header?.Examples != null ? new Dictionary<string, IOpenApiExample>(header.Examples) : null;
Content = header?.Content != null ? new Dictionary<string, OpenApiMediaType>(header.Content) : null;
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiMediaType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public OpenApiMediaType() { }
/// </summary>
public OpenApiMediaType(OpenApiMediaType? mediaType)
{
Schema = mediaType?.Schema != null ? new OpenApiSchema(mediaType.Schema) : null;
Schema = mediaType?.Schema?.CreateShallowCopy();
Example = mediaType?.Example != null ? JsonNodeCloneHelper.Clone(mediaType.Example) : null;
Examples = mediaType?.Examples != null ? new Dictionary<string, IOpenApiExample>(mediaType.Examples) : null;
Encoding = mediaType?.Encoding != null ? new Dictionary<string, OpenApiEncoding>(mediaType.Encoding) : null;
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public OpenApiParameter(IOpenApiParameter parameter)
Style = parameter.Style ?? Style;
Explode = parameter.Explode;
AllowReserved = parameter.AllowReserved;
Schema = parameter.Schema != null ? new OpenApiSchema(parameter.Schema) : null;
Schema = parameter.Schema.CreateShallowCopy();
Examples = parameter.Examples != null ? new Dictionary<string, IOpenApiExample>(parameter.Examples) : null;
Example = parameter.Example != null ? JsonNodeCloneHelper.Clone(parameter.Example) : null;
Content = parameter.Content != null ? new Dictionary<string, OpenApiMediaType>(parameter.Content) : null;
Expand Down
113 changes: 60 additions & 53 deletions src/Microsoft.OpenApi/Models/OpenApiSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,60 +186,61 @@ public OpenApiSchema() { }
/// Initializes a copy of <see cref="IOpenApiSchema"/> object
/// </summary>
/// <param name="schema">The schema object to copy from.</param>
public OpenApiSchema(IOpenApiSchema schema)
internal OpenApiSchema(IOpenApiSchema schema)
{
Title = schema?.Title ?? Title;
Id = schema?.Id ?? Id;
Const = schema?.Const ?? Const;
Schema = schema?.Schema ?? Schema;
Comment = schema?.Comment ?? Comment;
Vocabulary = schema?.Vocabulary != null ? new Dictionary<string, bool>(schema.Vocabulary) : null;
DynamicAnchor = schema?.DynamicAnchor ?? DynamicAnchor;
DynamicRef = schema?.DynamicRef ?? DynamicRef;
Definitions = schema?.Definitions != null ? new Dictionary<string, IOpenApiSchema>(schema.Definitions) : null;
UnevaluatedProperties = schema?.UnevaluatedProperties ?? UnevaluatedProperties;
V31ExclusiveMaximum = schema?.V31ExclusiveMaximum ?? V31ExclusiveMaximum;
V31ExclusiveMinimum = schema?.V31ExclusiveMinimum ?? V31ExclusiveMinimum;
Type = schema?.Type ?? Type;
Format = schema?.Format ?? Format;
Description = schema?.Description ?? Description;
Maximum = schema?.Maximum ?? Maximum;
ExclusiveMaximum = schema?.ExclusiveMaximum ?? ExclusiveMaximum;
Minimum = schema?.Minimum ?? Minimum;
ExclusiveMinimum = schema?.ExclusiveMinimum ?? ExclusiveMinimum;
MaxLength = schema?.MaxLength ?? MaxLength;
MinLength = schema?.MinLength ?? MinLength;
Pattern = schema?.Pattern ?? Pattern;
MultipleOf = schema?.MultipleOf ?? MultipleOf;
Default = schema?.Default != null ? JsonNodeCloneHelper.Clone(schema?.Default) : null;
ReadOnly = schema?.ReadOnly ?? ReadOnly;
WriteOnly = schema?.WriteOnly ?? WriteOnly;
AllOf = schema?.AllOf != null ? new List<IOpenApiSchema>(schema.AllOf) : null;
OneOf = schema?.OneOf != null ? new List<IOpenApiSchema>(schema.OneOf) : null;
AnyOf = schema?.AnyOf != null ? new List<IOpenApiSchema>(schema.AnyOf) : null;
Not = schema?.Not != null ? new OpenApiSchema(schema?.Not) : null;
Required = schema?.Required != null ? new HashSet<string>(schema.Required) : null;
Items = schema?.Items != null ? new OpenApiSchema(schema?.Items) : null;
MaxItems = schema?.MaxItems ?? MaxItems;
MinItems = schema?.MinItems ?? MinItems;
UniqueItems = schema?.UniqueItems ?? UniqueItems;
Properties = schema?.Properties != null ? new Dictionary<string, IOpenApiSchema>(schema.Properties) : null;
PatternProperties = schema?.PatternProperties != null ? new Dictionary<string, IOpenApiSchema>(schema.PatternProperties) : null;
MaxProperties = schema?.MaxProperties ?? MaxProperties;
MinProperties = schema?.MinProperties ?? MinProperties;
AdditionalPropertiesAllowed = schema?.AdditionalPropertiesAllowed ?? AdditionalPropertiesAllowed;
AdditionalProperties = schema?.AdditionalProperties != null ? new OpenApiSchema(schema?.AdditionalProperties) : null;
Discriminator = schema?.Discriminator != null ? new(schema?.Discriminator) : null;
Example = schema?.Example != null ? JsonNodeCloneHelper.Clone(schema?.Example) : null;
Examples = schema?.Examples != null ? new List<JsonNode>(schema.Examples) : null;
Enum = schema?.Enum != null ? new List<JsonNode>(schema.Enum) : null;
Nullable = schema?.Nullable ?? Nullable;
ExternalDocs = schema?.ExternalDocs != null ? new(schema?.ExternalDocs) : null;
Deprecated = schema?.Deprecated ?? Deprecated;
Xml = schema?.Xml != null ? new(schema?.Xml) : null;
Extensions = schema?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(schema.Extensions) : null;
Annotations = schema?.Annotations != null ? new Dictionary<string, object>(schema?.Annotations) : null;
UnrecognizedKeywords = schema?.UnrecognizedKeywords != null ? new Dictionary<string, JsonNode>(schema?.UnrecognizedKeywords) : null;
Utils.CheckArgumentNull(schema);
Title = schema.Title ?? Title;
Id = schema.Id ?? Id;
Const = schema.Const ?? Const;
Schema = schema.Schema ?? Schema;
Comment = schema.Comment ?? Comment;
Vocabulary = schema.Vocabulary != null ? new Dictionary<string, bool>(schema.Vocabulary) : null;
DynamicAnchor = schema.DynamicAnchor ?? DynamicAnchor;
DynamicRef = schema.DynamicRef ?? DynamicRef;
Definitions = schema.Definitions != null ? new Dictionary<string, IOpenApiSchema>(schema.Definitions) : null;
UnevaluatedProperties = schema.UnevaluatedProperties;
V31ExclusiveMaximum = schema.V31ExclusiveMaximum ?? V31ExclusiveMaximum;
V31ExclusiveMinimum = schema.V31ExclusiveMinimum ?? V31ExclusiveMinimum;
Type = schema.Type ?? Type;
Format = schema.Format ?? Format;
Description = schema.Description ?? Description;
Maximum = schema.Maximum ?? Maximum;
ExclusiveMaximum = schema.ExclusiveMaximum ?? ExclusiveMaximum;
Minimum = schema.Minimum ?? Minimum;
ExclusiveMinimum = schema.ExclusiveMinimum ?? ExclusiveMinimum;
MaxLength = schema.MaxLength ?? MaxLength;
MinLength = schema.MinLength ?? MinLength;
Pattern = schema.Pattern ?? Pattern;
MultipleOf = schema.MultipleOf ?? MultipleOf;
Default = schema.Default != null ? JsonNodeCloneHelper.Clone(schema.Default) : null;
ReadOnly = schema.ReadOnly;
WriteOnly = schema.WriteOnly;
AllOf = schema.AllOf != null ? new List<IOpenApiSchema>(schema.AllOf) : null;
OneOf = schema.OneOf != null ? new List<IOpenApiSchema>(schema.OneOf) : null;
AnyOf = schema.AnyOf != null ? new List<IOpenApiSchema>(schema.AnyOf) : null;
Not = schema.Not?.CreateShallowCopy();
Required = schema.Required != null ? new HashSet<string>(schema.Required) : null;
Items = schema.Items?.CreateShallowCopy();
MaxItems = schema.MaxItems ?? MaxItems;
MinItems = schema.MinItems ?? MinItems;
UniqueItems = schema.UniqueItems ?? UniqueItems;
Properties = schema.Properties != null ? new Dictionary<string, IOpenApiSchema>(schema.Properties) : null;
PatternProperties = schema.PatternProperties != null ? new Dictionary<string, IOpenApiSchema>(schema.PatternProperties) : null;
MaxProperties = schema.MaxProperties ?? MaxProperties;
MinProperties = schema.MinProperties ?? MinProperties;
AdditionalPropertiesAllowed = schema.AdditionalPropertiesAllowed;
AdditionalProperties = schema.AdditionalProperties?.CreateShallowCopy();
Discriminator = schema.Discriminator != null ? new(schema.Discriminator) : null;
Example = schema.Example != null ? JsonNodeCloneHelper.Clone(schema.Example) : null;
Examples = schema.Examples != null ? new List<JsonNode>(schema.Examples) : null;
Enum = schema.Enum != null ? new List<JsonNode>(schema.Enum) : null;
Nullable = schema.Nullable;
ExternalDocs = schema.ExternalDocs != null ? new(schema.ExternalDocs) : null;
Deprecated = schema.Deprecated;
Xml = schema.Xml != null ? new(schema.Xml) : null;
Extensions = schema.Extensions != null ? new Dictionary<string, IOpenApiExtension>(schema.Extensions) : null;
Annotations = schema.Annotations != null ? new Dictionary<string, object>(schema.Annotations) : null;
UnrecognizedKeywords = schema.UnrecognizedKeywords != null ? new Dictionary<string, JsonNode>(schema.UnrecognizedKeywords) : null;
}

/// <inheritdoc />
Expand Down Expand Up @@ -736,5 +737,11 @@ private void DowncastTypeArrayToV2OrV3(JsonSchemaType schemaType, IOpenApiWriter
}
}
}

/// <inheritdoc/>
public IOpenApiSchema CreateShallowCopy()
{
return new OpenApiSchema(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,12 @@ public override IOpenApiSchema CopyReferenceAsTargetElementWithOverrides(IOpenAp
{
return source is OpenApiSchema ? new OpenApiSchema(this) : source;
}
/// <inheritdoc/>
public IOpenApiSchema CreateShallowCopy()
{
return _target is null ?
new OpenApiSchemaReference(Reference.Id, Reference?.HostDocument, Reference?.ExternalResource) :
new OpenApiSchemaReference(_target, Reference.Id);
}
}
}
11 changes: 6 additions & 5 deletions src/Microsoft.OpenApi/Reader/V2/OpenApiOperationDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,13 @@ private static OpenApiRequestBody CreateFormBody(ParsingContext context, List<Op
k => k.Name,
v =>
{
var schema = new OpenApiSchema(v.Schema)
var schema = v.Schema.CreateShallowCopy();
schema.Description = v.Description;
if (schema is OpenApiSchema openApiSchema)
{
Description = v.Description,
Extensions = v.Extensions
};
return (IOpenApiSchema)schema;
openApiSchema.Extensions = v.Extensions;
}
return schema;
}),
Required = new HashSet<string>(formParameters.Where(static p => p.Required).Select(static p => p.Name), StringComparer.Ordinal)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,11 @@ public void TestSchemaCopyConstructorWithTypeArrayWorks()
};

// Act
var schemaWithArrayCopy = new OpenApiSchema(schemaWithTypeArray);
var schemaWithArrayCopy = schemaWithTypeArray.CreateShallowCopy() as OpenApiSchema;
schemaWithArrayCopy.Type = JsonSchemaType.String;

var simpleSchemaCopy = new OpenApiSchema(simpleSchema)
{
Type = JsonSchemaType.String | JsonSchemaType.Null
};
var simpleSchemaCopy = simpleSchema.CreateShallowCopy() as OpenApiSchema;
simpleSchemaCopy.Type = JsonSchemaType.String | JsonSchemaType.Null;

// Assert
Assert.NotEqual(schemaWithTypeArray.Type, schemaWithArrayCopy.Type);
Expand Down Expand Up @@ -294,7 +292,7 @@ public void CloningSchemaWithExamplesAndEnumsShouldSucceed()
Enum = [1, 2, 3]
};

var clone = new OpenApiSchema(schema);
var clone = schema.CreateShallowCopy() as OpenApiSchema;
clone.Examples.Add(4);
clone.Enum.Add(4);
clone.Default = 6;
Expand Down
12 changes: 5 additions & 7 deletions test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -471,10 +471,8 @@ public void OpenApiSchemaCopyConstructorSucceeds()
Format = "date"
};

var actualSchema = new OpenApiSchema(baseSchema)
{
Nullable = true
};
var actualSchema = baseSchema.CreateShallowCopy() as OpenApiSchema;
actualSchema.Nullable = true;

Assert.Equal(JsonSchemaType.String, actualSchema.Type);
Assert.Equal("date", actualSchema.Format);
Expand All @@ -493,7 +491,7 @@ public void OpenApiSchemaCopyConstructorWithAnnotationsSucceeds()
}
};

var actualSchema = new OpenApiSchema(baseSchema);
var actualSchema = baseSchema.CreateShallowCopy();

Assert.Equal(baseSchema.Annotations["key1"], actualSchema.Annotations["key1"]);

Expand Down Expand Up @@ -531,7 +529,7 @@ public void CloningSchemaExamplesWorks(JsonNode example)
};

// Act && Assert
var schemaCopy = new OpenApiSchema(schema);
var schemaCopy = schema.CreateShallowCopy();

// Act && Assert
schema.Example.Should().BeEquivalentTo(schemaCopy.Example, options => options
Expand All @@ -552,7 +550,7 @@ public void CloningSchemaExtensionsWorks()
};

// Act && Assert
var schemaCopy = new OpenApiSchema(schema);
var schemaCopy = schema.CreateShallowCopy() as OpenApiSchema;
Assert.Single(schemaCopy.Extensions);

// Act && Assert
Expand Down
Loading

0 comments on commit e4c14a4

Please sign in to comment.