File: CallSiteJsonFormatter.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj (Microsoft.Extensions.DependencyInjection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Text;
using Microsoft.Extensions.DependencyInjection.ServiceLookup;
 
namespace Microsoft.Extensions.DependencyInjection
{
    internal sealed class CallSiteJsonFormatter : CallSiteVisitor<CallSiteJsonFormatter.CallSiteFormatterContext, object?>
    {
        internal static readonly CallSiteJsonFormatter Instance = new CallSiteJsonFormatter();
 
        private CallSiteJsonFormatter()
        {
        }
 
        public string Format(ServiceCallSite callSite)
        {
            var stringBuilder = new StringBuilder();
            var context = new CallSiteFormatterContext(stringBuilder, 0, new HashSet<ServiceCallSite>());
 
            VisitCallSite(callSite, context);
 
            return stringBuilder.ToString();
        }
 
        protected override object? VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteFormatterContext argument)
        {
            argument.WriteProperty("implementationType", constructorCallSite.ImplementationType);
 
            if (constructorCallSite.ParameterCallSites.Length > 0)
            {
                argument.StartProperty("arguments");
 
                CallSiteFormatterContext childContext = argument.StartArray();
                foreach (ServiceCallSite parameter in constructorCallSite.ParameterCallSites)
                {
                    childContext.StartArrayItem();
                    VisitCallSite(parameter, childContext);
                }
                argument.EndArray();
            }
 
            return null;
        }
 
        protected override object? VisitCallSiteMain(ServiceCallSite callSite, CallSiteFormatterContext argument)
        {
            if (argument.ShouldFormat(callSite))
            {
                CallSiteFormatterContext childContext = argument.StartObject();
 
                childContext.WriteProperty("serviceType", callSite.ServiceType);
                childContext.WriteProperty("kind", callSite.Kind);
                childContext.WriteProperty("cache", callSite.Cache.Location);
 
                base.VisitCallSiteMain(callSite, childContext);
 
                argument.EndObject();
            }
            else
            {
                CallSiteFormatterContext childContext = argument.StartObject();
                childContext.WriteProperty("ref", callSite.ServiceType);
                argument.EndObject();
            }
 
            return null;
        }
 
        protected override object? VisitConstant(ConstantCallSite constantCallSite, CallSiteFormatterContext argument)
        {
            argument.WriteProperty("value", constantCallSite.DefaultValue ?? "");
 
            return null;
        }
 
        protected override object? VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, CallSiteFormatterContext argument)
        {
            return null;
        }
 
        protected override object? VisitIEnumerable(IEnumerableCallSite enumerableCallSite, CallSiteFormatterContext argument)
        {
            argument.WriteProperty("itemType", enumerableCallSite.ItemType);
            argument.WriteProperty("size", enumerableCallSite.ServiceCallSites.Length);
 
            if (enumerableCallSite.ServiceCallSites.Length > 0)
            {
                argument.StartProperty("items");
 
                CallSiteFormatterContext childContext = argument.StartArray();
                foreach (ServiceCallSite item in enumerableCallSite.ServiceCallSites)
                {
                    childContext.StartArrayItem();
                    VisitCallSite(item, childContext);
                }
                argument.EndArray();
            }
            return null;
        }
 
        protected override object? VisitFactory(FactoryCallSite factoryCallSite, CallSiteFormatterContext argument)
        {
            argument.WriteProperty("method", factoryCallSite.Factory.Method);
 
            return null;
        }
 
        internal struct CallSiteFormatterContext
        {
            private readonly HashSet<ServiceCallSite> _processedCallSites;
 
            public CallSiteFormatterContext(StringBuilder builder, int offset, HashSet<ServiceCallSite> processedCallSites)
            {
                Builder = builder;
                Offset = offset;
                _processedCallSites = processedCallSites;
                _firstItem = true;
            }
 
            private bool _firstItem;
 
            public int Offset { get; }
            public StringBuilder Builder { get; }
 
            public bool ShouldFormat(ServiceCallSite serviceCallSite)
            {
                return _processedCallSites.Add(serviceCallSite);
            }
 
            public CallSiteFormatterContext IncrementOffset()
            {
                return new CallSiteFormatterContext(Builder, Offset + 4, _processedCallSites)
                {
                    _firstItem = true
                };
            }
 
            public CallSiteFormatterContext StartObject()
            {
                Builder.Append('{');
                return IncrementOffset();
            }
 
            public void EndObject()
            {
                Builder.Append('}');
            }
 
            public void StartProperty(string name)
            {
                if (!_firstItem)
                {
                    Builder.Append(',');
                }
                else
                {
                    _firstItem = false;
                }
 
                Builder.Append('"').Append(name).Append("\":");
            }
 
            public void StartArrayItem()
            {
                if (!_firstItem)
                {
                    Builder.Append(',');
                }
                else
                {
                    _firstItem = false;
                }
            }
 
            public void WriteProperty(string name, object? value)
            {
                StartProperty(name);
                if (value != null)
                {
                    Builder.Append(" \"").Append(value).Append('"');
                }
                else
                {
                    Builder.Append("null");
                }
            }
 
            public CallSiteFormatterContext StartArray()
            {
                Builder.Append('[');
                return IncrementOffset();
            }
 
            public void EndArray()
            {
                Builder.Append(']');
            }
        }
    }
}