internal override OpenApiSchema FindOrAddReference()

in src/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration/ReferenceRegistries/SchemaReferenceRegistry.cs [62:311]


        internal override OpenApiSchema FindOrAddReference(Type input)
        {
            // Return empty schema when the type does not have a name. 
            // This can occur, for example, when a generic type without the generic argument specified
            // is passed in.
            if (input == null || input.FullName == null)
            {
                return new OpenApiSchema();
            }

            var key = GetKey(input);

            // If the schema already exists in the References, simply return.
            if (References.ContainsKey(key))
            {
                return new OpenApiSchema
                {
                    Reference = new OpenApiReference
                    {
                        Id = key,
                        Type = ReferenceType.Schema
                    }
                };
            }

            try
            {
                // There are multiple cases for input types that should be handled differently to match the OpenAPI spec.
                //
                // 1. Simple Type
                // 2. Enum Type
                // 3. Dictionary Type
                // 4. Enumerable Type
                // 5. Object Type
                var schema = new OpenApiSchema();

                if (input.IsSimple())
                {
                    schema = input.MapToOpenApiSchema();

                    // Certain simple types yield more specific information.
                    if (input == typeof(char))
                    {
                        schema.MinLength = 1;
                        schema.MaxLength = 1;
                    }
                    else if (input == typeof(Guid))
                    {
                        schema.Example = new OpenApiString(Guid.Empty.ToString());
                    }

                    return schema;
                }

                if (input == typeof(Stream) || input.BaseType == typeof(Stream))
                {
                    schema.Type = "string";
                    schema.Format = "binary";

                    return schema;
                }

                if (input.IsEnum)
                {
                    schema.Type = "string";
                    foreach (var name in Enum.GetNames(input))
                    {
                        schema.Enum.Add(new OpenApiString(name));
                    }

                    return schema;
                }

                if (input.IsDictionary())
                {
                    schema.Type = "object";
                    schema.AdditionalProperties = FindOrAddReference(input.GetGenericArguments()[1]);

                    return schema;
                }

                if (input.IsEnumerable())
                {
                    schema.Type = "array";

                    schema.Items = FindOrAddReference(input.GetEnumerableItemType());

                    return schema;
                }

                var nullableUnderlyingType = Nullable.GetUnderlyingType(input);

                if (nullableUnderlyingType?.IsEnum == true)
                {
                    schema.Type = "string";
                    schema.Nullable = true;

                    foreach (var name in nullableUnderlyingType.GetEnumNames())
                    {
                        schema.Enum.Add(new OpenApiString(name));
                    }

                    return schema;
                }

                schema.Type = "object";

                // Note this assignment is necessary to allow self-referencing type to finish
                // without causing stack overflow.
                // We can also assume that the schema is an object type at this point.
                References[key] = schema;

                var propertyNameDeclaringTypeMap = new Dictionary<string, Type>();
                var typeAttributes = input.GetCustomAttributes(false);

                foreach (var typeAttribute in typeAttributes)
                {
                    if (typeAttribute.GetType().FullName == "Newtonsoft.Json.JsonObjectAttribute")
                    {
                        var type = typeAttribute.GetType();
                        var namingStrategyInfo = type.GetProperty("NamingStrategyType");
                        if (namingStrategyInfo != null)
                        {
                            var namingStrategyValue = namingStrategyInfo.GetValue(typeAttribute, null);

                            if (namingStrategyValue?.ToString()
                                == "Newtonsoft.Json.Serialization.CamelCaseNamingStrategy")
                            {
                                _propertyNameResolver = new CamelCasePropertyNameResolver();
                            }
                        }
                    }
                }

                var a = input.FullName;

                foreach (var propertyInfo in input.GetProperties())
                {
                    var ignoreProperty = false;

                    var innerSchema = FindOrAddReference(propertyInfo.PropertyType);

                    var propertyName = _propertyNameResolver.ResolvePropertyName(propertyInfo);

                    // Construct property name like it shows up in documentation xml.
                    var propertyFullName = propertyInfo.DeclaringType.Namespace + "." +
                        propertyInfo.DeclaringType?.Name + "." + propertyInfo.Name;

                    if ( this._propertyDescriptionMap.ContainsKey( propertyFullName ) )
                    {
                        innerSchema.Description = this._propertyDescriptionMap[propertyFullName];
                    }

                    var attributes = propertyInfo.GetCustomAttributes(false);

                    foreach (var attribute in attributes)
                    {
                        if (attribute.GetType().FullName == "Newtonsoft.Json.JsonPropertyAttribute")
                        {
                            var type = attribute.GetType();
                            var requiredPropertyInfo = type.GetProperty("Required");

                            if (requiredPropertyInfo != null)
                            {
                                var requiredValue = Enum.GetName(
                                    requiredPropertyInfo.PropertyType,
                                    requiredPropertyInfo.GetValue(attribute, null));

                                if (requiredValue == "Always")
                                {
                                    schema.Required.Add(propertyName);
                                }
                            }
                        }

                        if (attribute.GetType().FullName == "Newtonsoft.Json.JsonIgnoreAttribute")
                        {
                            ignoreProperty = true;
                        }
                    }

                    if (ignoreProperty)
                    {
                        continue;
                    }

                    var propertyDeclaringType = propertyInfo.DeclaringType;

                    if (propertyNameDeclaringTypeMap.ContainsKey(propertyName))
                    {
                        var existingPropertyDeclaringType = propertyNameDeclaringTypeMap[propertyName];
                        var duplicateProperty = true;

                        if (existingPropertyDeclaringType != null && propertyDeclaringType != null)
                        {
                            if (propertyDeclaringType.IsSubclassOf(existingPropertyDeclaringType)
                                || (existingPropertyDeclaringType.IsInterface
                                && propertyDeclaringType.ImplementInterface(existingPropertyDeclaringType)))
                            {
                                // Current property is on a derived class and hides the existing
                                schema.Properties[propertyName] = innerSchema;
                                duplicateProperty = false;
                            }

                            if (existingPropertyDeclaringType.IsSubclassOf(propertyDeclaringType)
                                || (propertyDeclaringType.IsInterface
                                && existingPropertyDeclaringType.ImplementInterface(propertyDeclaringType)))
                            {
                                // current property is hidden by the existing so don't add it
                                continue;
                            }
                        }

                        if (duplicateProperty)
                        {
                            throw new AddingSchemaReferenceFailedException(
                                key,
                                string.Format(
                                    SpecificationGenerationMessages.DuplicateProperty,
                                    propertyName,
                                    input));
                        }
                    }

                    schema.Properties[propertyName] = innerSchema;
                    propertyNameDeclaringTypeMap.Add(propertyName, propertyDeclaringType);
                }

                References[key] = schema;

                return new OpenApiSchema
                {
                    Reference = new OpenApiReference
                    {
                        Id = key,
                        Type = ReferenceType.Schema
                    }
                };
            }
            catch (Exception e)
            {
                // Something went wrong while fetching schema, so remove the key if exists from the references.
                if (References.ContainsKey(key))
                {
                    References.Remove(key);
                }

                throw new AddingSchemaReferenceFailedException(key, e.Message);
            }
        }