Beispiel #1
0
        /// <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;
        }
Beispiel #2
0
        /// <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())
                                    .FindProperty(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;
            }
        }
Beispiel #3
0
        /// <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;
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        /// <summary>
        /// Determine whether a given property is defined in the schema or its ancestors. 
        /// Return the property schema if it is defined, or null if not.
        /// </summary>
        /// <param name="schema">A schema</param>
        /// <param name="propertyName">The property to search for</param>
        /// <returns></returns>
        public Schema FindProperty(Schema schema, string propertyName)
        {
            Schema returnedSchema = null;
            ExpandAllOf(schema);
            if (schema.Properties != null &&
                schema.Properties.ContainsKey(propertyName))
            {
                returnedSchema = schema.Properties[propertyName];
            }
            else
            {
                returnedSchema = FindProperty(schema.Extends, propertyName);
            }

            return returnedSchema;
        }
        public override IModelType BuildServiceType(string serviceTypeName)
        {
            // Check if already generated
            if (serviceTypeName != null && Modeler.GeneratedTypes.ContainsKey(serviceTypeName))
            {
                return Modeler.GeneratedTypes[serviceTypeName];
            }

            _schema = Modeler.Resolver.Unwrap(_schema);

            // If it's a primitive type, let the parent build service handle it
            if (_schema.IsPrimitiveType())
            {
                return _schema.GetBuilder(Modeler).ParentBuildServiceType(serviceTypeName);
            }

            // If it's known primary type, return that type
            var primaryType = _schema.GetSimplePrimaryType();
            if (primaryType != KnownPrimaryType.None)
            {
                return New<PrimaryType>(primaryType);
            }

            // Otherwise create new object type
            var objectType = New<CompositeType>(serviceTypeName,new 
            {
                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>(new
                {
                    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.Add(New<Property>(new
                {
                    Name = name,
                    ModelType = 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;
                        Schema refSchema = null;

                        if (property.Value.ReadOnly && property.Value.IsRequired)
                        {
                            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
                           Resources.ReadOnlyNotRequired, name, serviceTypeName));
                        }

                        if (property.Value.Reference != null)
                        {
                            propertyServiceTypeName = property.Value.Reference.StripDefinitionPath();
                            var unwrappedSchema = Modeler.Resolver.Unwrap(property.Value);

                            // For Enums use the referenced schema in order to set the correct property Type and Enum values
                            if (unwrappedSchema.Enum != null)
                            {
                                refSchema = new Schema().LoadFrom(unwrappedSchema);
                                if (property.Value.IsRequired)
                                {
                                    refSchema.IsRequired = property.Value.IsRequired;
                                }
                                //Todo: Remove the following when referenced descriptions are correctly ignored (Issue https://github.com/Azure/autorest/issues/1283)
                                refSchema.Description = property.Value.Description;
                            }
                        }
                        else
                        {
                            propertyServiceTypeName = serviceTypeName + "_" + property.Key;
                        }

                        var propertyType = refSchema != null
                                           ? refSchema.GetBuilder(Modeler).BuildServiceType(propertyServiceTypeName)
                                           : property.Value.GetBuilder(Modeler).BuildServiceType(propertyServiceTypeName);

                        var propertyObj = New<Property>(new
                        {
                            Name = name,
                            SerializedName = name,
                            ModelType = propertyType,
                            IsReadOnly = property.Value.ReadOnly,
                            Summary = property.Value.Title
                        });
                        PopulateParameter(propertyObj, refSchema != null ? refSchema : property.Value);
                        var propertyCompositeType = propertyType as CompositeType;
                        if (propertyObj.IsConstant ||
                            (propertyCompositeType != null
                                && propertyCompositeType.ContainsConstantProperties))
                        {
                            objectType.ContainsConstantProperties = true;
                        }

                        objectType.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;
        }
 public SchemaBuilder(Schema schema, SwaggerModeler modeler)
     : base(schema, modeler)
 {
     _schema = schema;
 }
Beispiel #8
0
        /// <summary>
        /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes.
        /// </summary>
        /// <param name="context">The modified document context.</param>
        /// <param name="previous">The original document model.</param>
        /// <returns>A list of messages from the comparison.</returns>
        public override IEnumerable <ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous)
        {
            if (previous == null)
            {
                throw new ArgumentNullException("previous");
            }

            context.CurrentRoot  = this;
            context.PreviousRoot = previous;

            base.Compare(context, previous);

            var previousDefinition = previous as ServiceDefinition;

            if (previousDefinition == null)
            {
                throw new ArgumentException("Comparing a service definition with something else.");
            }

            if (Info != null && previousDefinition.Info != null)
            {
                context.PushProperty("info");
                context.PushProperty("version");

                CompareVersions(context, Info.Version, previousDefinition.Info.Version);

                context.Pop();
                context.Pop();
            }

            if (context.Strict)
            {
                // There was no version change between the documents. This is not an error, but noteworthy.
                context.LogInfo(ComparisonMessages.NoVersionChange);
            }

            // Check that all the protocols of the old version are supported by the new version.

            context.PushProperty("schemes");
            foreach (var scheme in previousDefinition.Schemes)
            {
                if (!Schemes.Contains(scheme))
                {
                    context.LogBreakingChange(ComparisonMessages.ProtocolNoLongerSupported, scheme);
                }
            }
            context.Pop();

            // Check that all the request body formats that were accepted still are.

            context.PushProperty("consumes");
            foreach (var format in previousDefinition.Consumes)
            {
                if (!Consumes.Contains(format))
                {
                    context.LogBreakingChange(ComparisonMessages.RequestBodyFormatNoLongerSupported, format);
                }
            }
            context.Pop();

            // Check that all the response body formats were also supported by the old version.

            context.PushProperty("produces");
            foreach (var format in Produces)
            {
                if (!previousDefinition.Produces.Contains(format))
                {
                    context.LogBreakingChange(ComparisonMessages.ResponseBodyFormatNowSupported, format);
                }
            }
            context.Pop();

            // Check that no paths were removed, and compare the paths that are still there.

            var newPaths = RemovePathVariables(Paths);

            context.PushProperty("paths");
            foreach (var path in previousDefinition.Paths.Keys)
            {
                var p = Regex.Replace(path, @"\{\w*\}", @"{}");

                context.PushProperty(path);

                Dictionary <string, Operation> operations = null;
                if (!newPaths.TryGetValue(p, out operations))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedPath, path);
                }
                else
                {
                    Dictionary <string, Operation> previousOperations = previousDefinition.Paths[path];
                    foreach (var previousOperation in previousOperations)
                    {
                        Operation newOperation = null;
                        if (!operations.TryGetValue(previousOperation.Key, out newOperation))
                        {
                            context.LogBreakingChange(ComparisonMessages.RemovedOperation, previousOperation.Value.OperationId);
                        }
                    }

                    foreach (var operation in operations)
                    {
                        Operation previousOperation = null;
                        if (previousDefinition.Paths[path].TryGetValue(operation.Key, out previousOperation))
                        {
                            context.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }
            context.Pop();

            newPaths = RemovePathVariables(CustomPaths);

            context.PushProperty("x-ms-paths");
            foreach (var path in previousDefinition.CustomPaths.Keys)
            {
                var p = Regex.Replace(path, @"\{\w*\}", @"{}");

                context.PushProperty(path);

                Dictionary <string, Operation> operations = null;
                if (!newPaths.TryGetValue(p, out operations))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedPath, path);
                }
                else
                {
                    Dictionary <string, Operation> previousOperations = previousDefinition.CustomPaths[path];
                    foreach (var previousOperation in previousOperations)
                    {
                        Operation newOperation = null;
                        if (!operations.TryGetValue(previousOperation.Key, out newOperation))
                        {
                            context.LogBreakingChange(ComparisonMessages.RemovedOperation, previousOperation.Value.OperationId);
                        }
                    }

                    foreach (var operation in operations)
                    {
                        Operation previousOperation = null;
                        if (previousDefinition.CustomPaths[path].TryGetValue(operation.Key, out previousOperation))
                        {
                            context.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }
            context.Pop();

            ReferenceTrackSchemas(this);
            ReferenceTrackSchemas(previousDefinition);

            context.PushProperty("parameters");
            foreach (var def in previousDefinition.Parameters.Keys)
            {
                SwaggerParameter parameter = null;
                if (!Parameters.TryGetValue(def, out parameter))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedClientParameter, def);
                }
                else
                {
                    context.PushProperty(def);
                    parameter.Compare(context, previousDefinition.Parameters[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("responses");
            foreach (var def in previousDefinition.Responses.Keys)
            {
                OperationResponse response = null;
                if (!Responses.TryGetValue(def, out response))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def);
                }
                else
                {
                    context.PushProperty(def);
                    response.Compare(context, previousDefinition.Responses[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("definitions");
            foreach (var def in previousDefinition.Definitions.Keys)
            {
                Schema schema    = null;
                Schema oldSchema = previousDefinition.Definitions[def];

                if (!Definitions.TryGetValue(def, out schema))
                {
                    if (oldSchema.IsReferenced)
                    {
                        // It's only an error if the definition is referenced in the old service.
                        context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def);
                    }
                }
                else if (schema.IsReferenced && oldSchema.IsReferenced)
                {
                    context.PushProperty(def);
                    schema.Compare(context, previousDefinition.Definitions[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.Pop();

            return(context.Messages);
        }
Beispiel #9
0
        private void CompareProperties(ComparisonContext context, Schema priorSchema)
        {
            // Were any properties removed?

            if (priorSchema.Properties != null)
            {
                foreach (var def in priorSchema.Properties)
                {
                    Schema model = null;
                    if (Properties == null || !Properties.TryGetValue(def.Key, out model))
                    {
                        context.LogBreakingChange(ComparisonMessages.RemovedProperty1, def.Key);
                    }
                    else
                    {
                        context.Push(def.Key);
                        model.Compare(context, def.Value);
                        context.Pop();
                    }
                }
            }

            // Were any required properties added?

            if (Properties != null)
            {
                foreach (var def in Properties.Keys)
                {
                    Schema model = null;
                    if (priorSchema.Properties == null || !priorSchema.Properties.TryGetValue(def, out model) && Required.Contains(def))
                    {
                        context.LogBreakingChange(ComparisonMessages.AddedRequiredProperty1, def);
                    }
                }
            }
        }
Beispiel #10
0
        private void CompareAllOfs(ComparisonContext context, Schema priorSchema)
        {
            var different = 0;
            foreach (var schema in priorSchema.AllOf)
            {
                if (!AllOf.Select(s => s.Reference).ToArray().Contains(schema.Reference))
                {
                    different += 1;
                }
            }
            foreach (var schema in AllOf)
            {
                if (!priorSchema.AllOf.Select(s => s.Reference).ToArray().Contains(schema.Reference))
                {
                    different += 1;
                }
            }

            if (different > 0)
            {
                context.LogBreakingChange(ComparisonMessages.DifferentAllOf);
            }
        }