/// <summary> /// Normalize a swagger schema by dereferencing schema references and evaluating /// schema composition /// </summary> /// <param name="schema">The schema to normalize</param> /// <returns>A normalized swagger schema</returns> public Schema Unwrap(Schema schema) { if (schema == null) { return null; } Schema unwrappedSchema = schema; // If referencing global definitions serializationProperty if (schema.Reference != null) { unwrappedSchema = Dereference(schema.Reference); } ExpandAllOf(unwrappedSchema); return unwrappedSchema; }
public override IType BuildServiceType(string serviceTypeName) { // Check if already generated if (serviceTypeName != null && Modeler.GeneratedTypes.ContainsKey(serviceTypeName)) { return Modeler.GeneratedTypes[serviceTypeName]; } _schema = Modeler.Resolver.Unwrap(_schema); // If primitive type if ((_schema.Type != null && _schema.Type != DataType.Object) || _schema.AdditionalProperties != null) { return _schema.GetBuilder(Modeler).ParentBuildServiceType(serviceTypeName); } // If the object does not have any properties, treat it as raw json (i.e. object) if (_schema.Properties.IsNullOrEmpty() && string.IsNullOrEmpty(_schema.Extends)) { return PrimaryType.Object; } // Otherwise create new object type var objectType = new CompositeType { Name = serviceTypeName, SerializedName = serviceTypeName, Documentation = _schema.Description }; // Put this in already generated types serializationProperty Modeler.GeneratedTypes[serviceTypeName] = objectType; if (_schema.Properties != null) { // Visit each property and recursively build service types foreach (var property in _schema.Properties) { string name = property.Key; if (name != _schema.Discriminator) { string propertyServiceTypeName; if (property.Value.Reference != null) { propertyServiceTypeName = property.Value.Reference.StripDefinitionPath(); } else { propertyServiceTypeName = serviceTypeName + "_" + property.Key; } var propertyType = property.Value.GetBuilder(Modeler).BuildServiceType(propertyServiceTypeName); var propertyObj = new Property { Name = name, SerializedName = name, Type = propertyType, IsRequired = property.Value.IsRequired }; //propertyObj.Type = objectType; propertyObj.Documentation = property.Value.Description; var enumType = propertyType as EnumType; if (enumType != null) { if (propertyObj.Documentation == null) { propertyObj.Documentation = string.Empty; } else { propertyObj.Documentation = propertyObj.Documentation.TrimEnd('.') + ". "; } propertyObj.Documentation += "Possible values for this property include: " + string.Join(", ", enumType.Values.Select(v => string.Format(CultureInfo.InvariantCulture, "'{0}'", v.Name))) + "."; } propertyObj.IsReadOnly = property.Value.ReadOnly; objectType.Properties.Add(propertyObj); } else { objectType.PolymorphicDiscriminator = name; } } } // Copy over extensions _schema.Extensions.ForEach(e => objectType.Extensions[e.Key] = e.Value); // Put this in the extended type serializationProperty for building method return type in the end if (_schema.Extends != null) { Modeler.ExtendedTypes[serviceTypeName] = _schema.Extends.StripDefinitionPath(); } return objectType; }
public SchemaBuilder(Schema schema, SwaggerModeler modeler) : base(schema, modeler) { _schema = schema; }
public override IType BuildServiceType(string serviceTypeName) { // Check if already generated if (serviceTypeName != null && Modeler.GeneratedTypes.ContainsKey(serviceTypeName)) { return Modeler.GeneratedTypes[serviceTypeName]; } _schema = Modeler.Resolver.Unwrap(_schema); // If primitive type if ((_schema.Type != null && _schema.Type != DataType.Object) || _schema.AdditionalProperties != null) { return _schema.GetBuilder(Modeler).ParentBuildServiceType(serviceTypeName); } // If object with file format treat as stream if (_schema.Type != null && _schema.Type == DataType.Object && "file".Equals(SwaggerObject.Format, StringComparison.OrdinalIgnoreCase)) { return PrimaryType.Stream; } // If the object does not have any properties, treat it as raw json (i.e. object) if (_schema.Properties.IsNullOrEmpty() && string.IsNullOrEmpty(_schema.Extends)) { return PrimaryType.Object; } // Otherwise create new object type var objectType = new CompositeType { Name = serviceTypeName, SerializedName = serviceTypeName, Documentation = _schema.Description }; // Put this in already generated types serializationProperty Modeler.GeneratedTypes[serviceTypeName] = objectType; if (_schema.Properties != null) { // Visit each property and recursively build service types foreach (var property in _schema.Properties) { string name = property.Key; if (name != _schema.Discriminator) { string propertyServiceTypeName; if (property.Value.Reference != null) { propertyServiceTypeName = property.Value.Reference.StripDefinitionPath(); } else { propertyServiceTypeName = serviceTypeName + "_" + property.Key; } var propertyType = property.Value.GetBuilder(Modeler).BuildServiceType(propertyServiceTypeName); var propertyObj = new Property { Name = name, SerializedName = name, Type = propertyType, IsRequired = property.Value.IsRequired, IsReadOnly = property.Value.ReadOnly, DefaultValue = property.Value.Default }; SetConstraints(propertyObj.Constraints, property.Value); //propertyObj.Type = objectType; propertyObj.Documentation = property.Value.Description; var enumType = propertyType as EnumType; if (enumType != null) { if (propertyObj.Documentation == null) { propertyObj.Documentation = string.Empty; } else { propertyObj.Documentation = propertyObj.Documentation.TrimEnd('.') + ". "; } propertyObj.Documentation += "Possible values for this property include: " + string.Join(", ", enumType.Values.Select(v => string.Format(CultureInfo.InvariantCulture, "'{0}'", v.Name))) + "."; } objectType.Properties.Add(propertyObj); } else { objectType.PolymorphicDiscriminator = name; } } } // Copy over extensions _schema.Extensions.ForEach(e => objectType.Extensions[e.Key] = e.Value); if (_schema.Extends != null) { // Optionally override the discriminator value for polymorphic types. We expect this concept to be // added to Swagger at some point, but until it is, we use an extension. object discriminatorValueExtension; if (objectType.Extensions.TryGetValue(DiscriminatorValueExtension, out discriminatorValueExtension)) { string discriminatorValue = discriminatorValueExtension as string; if (discriminatorValue != null) { objectType.SerializedName = discriminatorValue; } } // Put this in the extended type serializationProperty for building method return type in the end Modeler.ExtendedTypes[serviceTypeName] = _schema.Extends.StripDefinitionPath(); } return objectType; }
/// <summary> /// Evaluate the composition of properties for a swagger spec and save the /// evaluated form in the specification. This transformation is idempotent /// </summary> /// <param name="schema">The swagger schema to evaluate.</param> public void ExpandAllOf(Schema schema) { if (schema == null) { throw new ArgumentNullException("schema"); } if (schema.AllOf != null) { CheckCircularAllOf(schema, null, null); var references = schema.AllOf.Where(s => s.Reference != null).ToList(); if (references.Count == 1) { if (schema.Extends != null) { throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, Properties.Resources.InvalidTypeExtendsWithAllOf, schema.Title)); } schema.Extends = references[0].Reference; schema.AllOf.Remove(references[0]); } var parentSchema = schema.Extends; var propertiesOnlySchema = new Schema { Properties = schema.Properties }; var schemaList = new List<Schema>().Concat(schema.AllOf) .Concat(new List<Schema> {propertiesOnlySchema}); schema.Properties = new Dictionary<string, Schema>(); foreach (var componentSchema in schemaList) { // keep the same resolver state for each of the children var unwrappedComponent = ((SchemaResolver) Clone()).Unwrap( componentSchema); if (unwrappedComponent != null && unwrappedComponent.Properties != null) { foreach (var propertyName in unwrappedComponent.Properties.Keys) { var unwrappedProperty = unwrappedComponent.Properties[propertyName]; if (schema.Properties.ContainsKey(propertyName)) { if (!SchemaTypesAreEquivalent( schema.Properties[propertyName], unwrappedProperty)) { throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, Properties.Resources.IncompatibleTypesInSchemaComposition, propertyName, unwrappedComponent.Properties[propertyName].Type, schema.Properties[propertyName].Type, schema.Title)); } } else { var parentProperty = ((SchemaResolver) Clone()) .FindParentProperty(parentSchema, propertyName); if (parentProperty != null) { if (!SchemaTypesAreEquivalent(parentProperty, unwrappedProperty)) { throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, Properties.Resources.IncompatibleTypesInBaseSchema, propertyName, parentProperty.Type, unwrappedProperty.Type, schema.Title)); } } else { schema.Properties[propertyName] = unwrappedProperty; } } } } if (unwrappedComponent != null && unwrappedComponent.Required != null) { var requiredProperties = schema.Required ?? new List<string>(); foreach (var requiredProperty in unwrappedComponent.Required) { if (!requiredProperties.Contains(requiredProperty)) { requiredProperties.Add(requiredProperty); } } schema.Required = requiredProperties; } } schema.AllOf = null; } }
/// <summary> /// Determine equivalence between the types described by two schemas. /// Limit the comparison to exclude comparison of complexe inline schemas. /// </summary> /// <param name="parentProperty"></param> /// <param name="unwrappedProperty"></param> /// <returns></returns> private bool SchemaTypesAreEquivalent(Schema parentProperty, Schema unwrappedProperty) { Debug.Assert(parentProperty != null && unwrappedProperty != null); if (parentProperty == null) { throw new ArgumentNullException("parentProperty"); } if (unwrappedProperty == null) { throw new ArgumentNullException("unwrappedProperty"); } if ((parentProperty.Type == null || parentProperty.Type == DataType.Object) && (unwrappedProperty.Type == null || unwrappedProperty.Type == DataType.Object)) { var parentPropertyToCompare = parentProperty; var unwrappedPropertyToCompare = unwrappedProperty; if (!string.IsNullOrEmpty(parentProperty.Reference)) { parentPropertyToCompare = Dereference(parentProperty.Reference); } if (!string.IsNullOrEmpty(unwrappedProperty.Reference)) { unwrappedPropertyToCompare = Dereference(unwrappedProperty.Reference); } if (parentPropertyToCompare == unwrappedPropertyToCompare) { return true; // when fully dereferenced, they can refer to the same thing } // or they can refer to different things... but there can be an inheritance relation... while (unwrappedPropertyToCompare != null && unwrappedPropertyToCompare.Extends != null) { unwrappedPropertyToCompare = Dereference(unwrappedPropertyToCompare.Extends); if (unwrappedPropertyToCompare == parentPropertyToCompare) { return true; } } return false; } if (parentProperty.Type == DataType.Array && unwrappedProperty.Type == DataType.Array) { return SchemaTypesAreEquivalent(parentProperty.Items, unwrappedProperty.Items); } return parentProperty.Type == unwrappedProperty.Type && parentProperty.Format == unwrappedProperty.Format; }
void CheckCircularAllOf(Schema schema, HashSet<Schema> visited, Stack<string> referenceChain) { visited = visited ?? new HashSet<Schema>(); referenceChain = referenceChain ?? new Stack<string>(); if (!visited.Add(schema)) // was already present in the set { var setDescription = "(" + String.Join(", ", referenceChain) + ")"; throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, Properties.Resources.CircularBaseSchemaSet, setDescription)); } if (schema.AllOf != null) { foreach (var reference in schema.AllOf.Select(s => s.Reference).Where(r => r != null)) { referenceChain.Push(reference); var deref = Dereference(reference); CheckCircularAllOf(deref, visited, referenceChain); Debug.Assert(reference == referenceChain.Peek()); referenceChain.Pop(); } } visited.Remove(schema); }
public override IType BuildServiceType(string serviceTypeName) { // Check if already generated if (serviceTypeName != null && Modeler.GeneratedTypes.ContainsKey(serviceTypeName)) { return Modeler.GeneratedTypes[serviceTypeName]; } _schema = Modeler.Resolver.Unwrap(_schema); // If primitive type if (_schema.Type != null && _schema.Type != DataType.Object || ( _schema.AdditionalProperties != null && _schema.Properties.IsNullOrEmpty() )) { // Notes: // 'additionalProperties' on a type AND no defined 'properties', indicates that // this type is a Dictionary. (and is handled by ObjectBuilder) return _schema.GetBuilder(Modeler).ParentBuildServiceType(serviceTypeName); } // If object with file format treat as stream if (_schema.Type != null && _schema.Type == DataType.Object && "file".Equals(SwaggerObject.Format, StringComparison.OrdinalIgnoreCase)) { return new PrimaryType(KnownPrimaryType.Stream); } // If the object does not have any properties, treat it as raw json (i.e. object) if (_schema.Properties.IsNullOrEmpty() && string.IsNullOrEmpty(_schema.Extends) && _schema.AdditionalProperties == null) { return new PrimaryType(KnownPrimaryType.Object); } // Otherwise create new object type var objectType = new CompositeType { Name = serviceTypeName, SerializedName = serviceTypeName, Documentation = _schema.Description, ExternalDocsUrl = _schema.ExternalDocs?.Url, Summary = _schema.Title }; // Put this in already generated types serializationProperty Modeler.GeneratedTypes[serviceTypeName] = objectType; if (_schema.Type == DataType.Object && _schema.AdditionalProperties != null) { // this schema is defining 'additionalProperties' which expects to create an extra // property that will catch all the unbound properties during deserialization. var name = "additionalProperties"; var propertyType = new DictionaryType { ValueType = _schema.AdditionalProperties.GetBuilder(Modeler).BuildServiceType( _schema.AdditionalProperties.Reference != null ? _schema.AdditionalProperties.Reference.StripDefinitionPath() : serviceTypeName + "Value"), SupportsAdditionalProperties = true }; // now add the extra property to the type. objectType.Properties.Add(new Property { Name = name, Type = propertyType, Documentation = "Unmatched properties from the message are deserialized this collection" }); } if (_schema.Properties != null) { // Visit each property and recursively build service types foreach (var property in _schema.Properties) { string name = property.Key; if (name != _schema.Discriminator) { string propertyServiceTypeName; if (property.Value.Reference != null) { propertyServiceTypeName = property.Value.Reference.StripDefinitionPath(); } else { propertyServiceTypeName = serviceTypeName + "_" + property.Key; } var propertyType = property.Value.GetBuilder(Modeler).BuildServiceType(propertyServiceTypeName); if (property.Value.ReadOnly && property.Value.IsRequired) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.ReadOnlyNotRequired, name, serviceTypeName)); } var propertyObj = new Property { Name = name, SerializedName = name, Type = propertyType, IsReadOnly = property.Value.ReadOnly, Summary = property.Value.Title }; PopulateParameter(propertyObj, property.Value); var propertyCompositeType = propertyType as CompositeType; if (propertyObj.IsConstant || (propertyCompositeType != null && propertyCompositeType.ContainsConstantProperties)) { objectType.ContainsConstantProperties = true; } objectType.Properties.Add(propertyObj); } else { objectType.PolymorphicDiscriminator = name; } } } // Copy over extensions _schema.Extensions.ForEach(e => objectType.Extensions[e.Key] = e.Value); if (_schema.Extends != null) { // Optionally override the discriminator value for polymorphic types. We expect this concept to be // added to Swagger at some point, but until it is, we use an extension. object discriminatorValueExtension; if (objectType.Extensions.TryGetValue(DiscriminatorValueExtension, out discriminatorValueExtension)) { string discriminatorValue = discriminatorValueExtension as string; if (discriminatorValue != null) { objectType.SerializedName = discriminatorValue; } } // Put this in the extended type serializationProperty for building method return type in the end Modeler.ExtendedTypes[serviceTypeName] = _schema.Extends.StripDefinitionPath(); } return objectType; }
/// <summary> /// Determine equivalence between the types described by two schemas. /// Limit the comparison to exclude comparison of complexe inline schemas. /// </summary> /// <param name="parentProperty"></param> /// <param name="unwrappedProperty"></param> /// <returns></returns> private bool SchemaTypesAreEquivalent(Schema parentProperty, Schema unwrappedProperty) { Debug.Assert(parentProperty != null && unwrappedProperty != null); if (parentProperty == null) { throw new ArgumentNullException("parentProperty"); } if (unwrappedProperty == null) { throw new ArgumentNullException("unwrappedProperty"); } if ((parentProperty.Type == null || parentProperty.Type == DataType.Object) && (unwrappedProperty.Type == null || unwrappedProperty.Type == DataType.Object)) { if (!string.IsNullOrEmpty(parentProperty.Reference) || !string.IsNullOrEmpty(unwrappedProperty.Reference)) { return parentProperty.Reference == unwrappedProperty.Reference; } // do not compare inline schemas return false; } if (parentProperty.Type == DataType.Array && unwrappedProperty.Type == DataType.Array) { return SchemaTypesAreEquivalent(parentProperty.Items, unwrappedProperty.Items); } return parentProperty.Type == unwrappedProperty.Type && parentProperty.Format == unwrappedProperty.Format; }