Initial commit

This commit is contained in:
Peter 2020-03-10 01:01:59 +01:00
parent a6a2e64688
commit 64484d4981
Signed by: prskr
GPG key ID: 2285CF1BAF6ACCAA
26 changed files with 670 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
###############
# folder #
###############
/**/DROP/
/**/TEMP/
/**/packages/
/**/bin/
/**/obj/
_site
.idea/

5
api/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
###############
# temp file #
###############
*.yml
.manifest

2
api/index.md Normal file
View file

@ -0,0 +1,2 @@
# PLACEHOLDER
TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*!

1
articles/intro.md Normal file
View file

@ -0,0 +1 @@
# Add your introductions here!

2
articles/toc.yml Normal file
View file

@ -0,0 +1,2 @@
- name: Introduction
href: intro.md

64
docfx.json Normal file
View file

@ -0,0 +1,64 @@
{
"metadata": [
{
"src": [
{
"files": [
"src/**.csproj"
]
}
],
"dest": "api",
"disableGitFeatures": false,
"disableDefaultFilter": false
}
],
"build": {
"content": [
{
"files": [
"api/**.yml",
"api/index.md"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
}
],
"resource": [
{
"files": [
"images/**"
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"statictoc"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
}
}

4
index.md Normal file
View file

@ -0,0 +1,4 @@
# This is the **HOMEPAGE**.
Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
## Quick Start Notes:
1. Add images to the *images* folder if the file is referencing an image.

View file

@ -0,0 +1,35 @@
using System;
using Tand.Core.Models;
namespace Tand.Core.Tests
{
public class LogTarget<T> : ITandTarget<T>
{
private readonly Action<string> _logHandle;
public LogTarget(Action<string> logHandle)
{
_logHandle = logHandle;
}
public void OnEnterMethod(CallEnterContext<T> enterContext)
{
_logHandle(enterContext.Instance.ToString());
foreach (var (name, val) in enterContext.Arguments)
{
_logHandle($"name: {name}, value: {val}");
}
}
public void OnLeaveMethod(CallLeaveContext<T> callLeaveContext)
{
_logHandle(callLeaveContext.Instance.ToString());
foreach (var (name, val) in callLeaveContext.Arguments)
{
_logHandle($"name: {name}, value: {val}");
}
_logHandle($"result: {callLeaveContext.CallResult}");
}
}
}

View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tand.Core\Tand.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,60 @@
using System;
using Xunit;
using Xunit.Abstractions;
namespace Tand.Core.Tests
{
public class TandTest
{
private readonly ITestOutputHelper _outputHelper;
public TandTest(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
}
[Fact]
public void GenerateTand()
{
var tand = new Tand(new SampleResolver(_outputHelper));
var sample = tand.DecorateWithTand<ITAndSample, TandSample>(new TandSample());
var result = sample.LogMyParams("Hello, World", 42);
_outputHelper.WriteLine($"Got result: {result}");
}
}
public class SampleResolver : IDependencyResolver
{
private readonly ITestOutputHelper _outputHelper;
public SampleResolver(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
}
public ITandTarget<T> TargetOfType<T>(Type type)
{
return new LogTarget<T>(_outputHelper.WriteLine);
}
}
public interface ITAndSample
{
[Tand(typeof(LogTarget<TandSample>))]
int LogMyParams(string s, int i);
}
public class TandSample : ITAndSample
{
private int _counter;
public string ContextSample { get; set; }
public int LogMyParams(string s, int i)
{
return ++_counter;
}
}
}

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Tand.Core.Models;
namespace Tand.Core
{
public readonly struct CallEnterContext<T>
{
private readonly IDictionary<string, object> _methodArgs;
public CallEnterContext(T instance, IDictionary<string, object> args)
{
Instance = instance;
_methodArgs = args;
}
public T Instance { get; }
public object? this[string argName] => _methodArgs.ContainsKey(argName) ? _methodArgs[argName] : null;
public IEnumerable<MethodArgument> Arguments => _methodArgs.Select(kv => new MethodArgument(kv.Key, kv.Value));
}
}

View file

@ -0,0 +1,9 @@
using System;
namespace Tand.Core
{
public interface IDependencyResolver
{
ITandTarget<T> TargetOfType<T>(Type type);
}
}

View file

@ -0,0 +1,11 @@
using Tand.Core.Models;
namespace Tand.Core
{
public interface ITandTarget<T>
{
void OnEnterMethod(CallEnterContext<T> enterContext);
void OnLeaveMethod(CallLeaveContext<T> leaveContext);
}
}

View file

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Linq;
namespace Tand.Core.Models
{
public readonly struct CallLeaveContext<T>
{
private readonly IDictionary<string, object> _methodArgs;
public CallLeaveContext(
T instance,
IDictionary<string, object> args,
object callResult
)
{
Instance = instance;
_methodArgs = args;
CallResult = callResult;
}
public T Instance { get; }
public object CallResult { get; }
public object? this[string argName] => _methodArgs.ContainsKey(argName) ? _methodArgs[argName] : null;
public IEnumerable<MethodArgument> Arguments => _methodArgs.Select(kv => new MethodArgument(kv.Key, kv.Value));
}
}

View file

@ -0,0 +1,21 @@
namespace Tand.Core.Models
{
public readonly struct MethodArgument
{
public MethodArgument(string name, object value)
{
Name = name;
Value = value;
}
public string Name { get; }
public object Value { get; }
public void Deconstruct(out string name, out object value)
{
name = Name;
value = Value;
}
}
}

View file

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>8</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,38 @@
using System.Reflection;
namespace Tand.Core
{
public class Tand
{
private readonly IDependencyResolver _dependencyResolver;
public Tand(IDependencyResolver dependencyResolver)
{
_dependencyResolver = dependencyResolver;
}
public TService DecorateWithTand<TService, TImplementation>(TImplementation toBeDecorated)
where TService : class
where TImplementation : class, TService
{
var proxy = DispatchProxy.Create<TService, TandProxy<TService>>();
InitProxy<TService, TImplementation>(proxy, toBeDecorated);
return proxy;
}
private void InitProxy<TService, TImplementation>(object proxyObj, TService toBeDecorated)
{
var proxy = proxyObj switch
{
TandProxy<TService> tp => tp,
_ => null
};
if (proxy == null) return;
proxy.Decorated = toBeDecorated;
proxy.ImplementationType = typeof(TImplementation);
proxy.DependencyResolver = _dependencyResolver;
}
}
}

View file

@ -0,0 +1,16 @@
using System;
namespace Tand.Core
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TandAttribute : Attribute
{
public TandAttribute(Type targetType)
{
TargetType = targetType;
}
public Type TargetType { get; }
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Tand.Core.Models;
namespace Tand.Core
{
public class TandProxy<T> : DispatchProxy
{
private readonly IDictionary<int, Type[]> _targetCache;
public TandProxy()
{
_targetCache = new ConcurrentDictionary<int, Type[]>();
}
public Type ImplementationType { get; set; }
public T Decorated { private get; set; }
public IDependencyResolver DependencyResolver { get; set; }
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var mappedMethodArgs = CollectArgs(targetMethod, args);
var enterContext = new CallEnterContext<T>(Decorated, mappedMethodArgs);
var targets = TargetTypesForMethod(targetMethod);
foreach (var target in targets)
{
target.OnEnterMethod(enterContext);
}
var result = targetMethod.Invoke(Decorated, args);
var leaveContext = new CallLeaveContext<T>(Decorated, mappedMethodArgs, result);
foreach (var target in targets)
{
target.OnLeaveMethod(leaveContext);
}
return result;
}
private static IDictionary<string, object> CollectArgs(MethodBase methodInfo, IReadOnlyList<object> argValues)
{
var result = new Dictionary<string, object>();
var parameters = methodInfo.GetParameters();
for (var i = 0; i < parameters.Length && i < argValues.Count; i++)
{
result.Add(parameters[i].Name, argValues[i]);
}
return result;
}
private ICollection<ITandTarget<T>> TargetTypesForMethod(MethodInfo methodInfo)
{
var hash = methodInfo.GetHashCode();
if (!_targetCache.ContainsKey(hash))
{
_targetCache[hash] = methodInfo.GetCustomAttributes<TandAttribute>()
.Select(attr => attr.TargetType)
.ToArray();
}
return _targetCache[hash]
.Select(type => DependencyResolver.TargetOfType<T>(type))
.ToList();
}
}
}

View file

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="coverlet.collector" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tand.Extensions.DependencyInjection\Tand.Extensions.DependencyInjection.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,60 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Tand.Core;
using Tand.Core.Models;
using Xunit;
namespace Tand.Extensions.DependencyInjection.Tests
{
public class TandServiceExtensionsTests
{
public const string Greeting = "Hello, World!";
[Fact]
public void RegisterService_RegisterSampleService_SuccessfullyResolve()
{
var calledOnEnter = false;
var calledOnExit = false;
var servides = new ServiceCollection();
servides.AddTand();
servides.AddSingleton(new TestDecorator(_ => { calledOnEnter = true;}, _ => { calledOnExit = true;}));
servides.AddTandTransient<ISampleService, SampleServiceImpl>();
var provider = servides.BuildServiceProvider();
var sampleService = provider.GetService<ISampleService>();
Assert.NotNull(sampleService);
var result = sampleService.Greet();
Assert.Equal(Greeting, result);
Assert.True(calledOnEnter);
Assert.True(calledOnExit);
}
}
public class TestDecorator : ITandTarget<ISampleService>
{
private readonly Action<CallEnterContext<ISampleService>> _onEnterHandle;
private readonly Action<CallLeaveContext<ISampleService>> _onLeaveHandle;
public TestDecorator(Action<CallEnterContext<ISampleService>> onEnterHandle, Action<CallLeaveContext<ISampleService>> onLeaveHandle)
{
_onEnterHandle = onEnterHandle;
_onLeaveHandle = onLeaveHandle;
}
public void OnEnterMethod(CallEnterContext<ISampleService> enterContext) => _onEnterHandle(enterContext);
public void OnLeaveMethod(CallLeaveContext<ISampleService> leaveContext) => _onLeaveHandle(leaveContext);
}
public interface ISampleService
{
[Tand(typeof(TestDecorator))]
string Greet();
}
public class SampleServiceImpl : ISampleService
{
public string Greet() => TandServiceExtensionsTests.Greeting;
}
}

View file

@ -0,0 +1,21 @@
using System;
using Tand.Core;
namespace Tand.Extensions.DependencyInjection
{
public class DependencyResolverProxy : IDependencyResolver
{
private readonly IServiceProvider _serviceProvider;
public DependencyResolverProxy(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public ITandTarget<T> TargetOfType<T>(Type type) => _serviceProvider.GetService(type) switch
{
ITandTarget<T> target => target,
_ => throw new ArgumentException()
};
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tand.Core\Tand.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,31 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Tand.Extensions.DependencyInjection
{
public static class TandServiceExtensions
{
public static void AddTand(this IServiceCollection serviceCollection)
{
serviceCollection.TryAddSingleton(sp => new Tand.Core.Tand(new DependencyResolverProxy(sp)));
}
public static IServiceCollection AddTandTransient<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.AddTransient<TImplementation>();
services.AddTransient(GetTandForType<TService, TImplementation>);
return services;
}
private static TService GetTandForType<TService, TImplementation>(IServiceProvider serviceProvider) where TImplementation : class, TService where TService : class
{
var tand = serviceProvider.GetService<Tand.Core.Tand>();
var instance = serviceProvider.GetService<TImplementation>();
var proxy = tand.DecorateWithTand<TService, TImplementation>(instance);
return proxy;
}
}
}

86
src/tand/Tand.sln Normal file
View file

@ -0,0 +1,86 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tand.Core", "Tand.Core\Tand.Core.csproj", "{994166B2-862B-451B-B3E5-B2797EFF7022}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4A0DFFF5-7BB2-481A-BB0F-152D89823F48}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C51CA370-A0C7-4229-90FA-ADF7B51DE021}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tand.Core.Tests", "Tand.Core.Tests\Tand.Core.Tests.csproj", "{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tand.Extensions.DependencyInjection", "Tand.Extensions.DependencyInjection\Tand.Extensions.DependencyInjection.csproj", "{C535C043-7A0E-4F17-89E4-493E7805CFAC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tand.Extensions.DependencyInjection.Tests", "Tand.Extensions.DependencyInjection.Tests\Tand.Extensions.DependencyInjection.Tests.csproj", "{D1287B4D-319A-4D7A-BB5B-93C4E8320480}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|Any CPU.Build.0 = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|x64.ActiveCfg = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|x64.Build.0 = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|x86.ActiveCfg = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Debug|x86.Build.0 = Debug|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|Any CPU.ActiveCfg = Release|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|Any CPU.Build.0 = Release|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|x64.ActiveCfg = Release|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|x64.Build.0 = Release|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|x86.ActiveCfg = Release|Any CPU
{994166B2-862B-451B-B3E5-B2797EFF7022}.Release|x86.Build.0 = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|x64.ActiveCfg = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|x64.Build.0 = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|x86.ActiveCfg = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Debug|x86.Build.0 = Debug|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|Any CPU.Build.0 = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|x64.ActiveCfg = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|x64.Build.0 = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|x86.ActiveCfg = Release|Any CPU
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7}.Release|x86.Build.0 = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|x64.ActiveCfg = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|x64.Build.0 = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|x86.ActiveCfg = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Debug|x86.Build.0 = Debug|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|Any CPU.Build.0 = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|x64.ActiveCfg = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|x64.Build.0 = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|x86.ActiveCfg = Release|Any CPU
{C535C043-7A0E-4F17-89E4-493E7805CFAC}.Release|x86.Build.0 = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|x64.ActiveCfg = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|x64.Build.0 = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|x86.ActiveCfg = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Debug|x86.Build.0 = Debug|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|Any CPU.Build.0 = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|x64.ActiveCfg = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|x64.Build.0 = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|x86.ActiveCfg = Release|Any CPU
{D1287B4D-319A-4D7A-BB5B-93C4E8320480}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{994166B2-862B-451B-B3E5-B2797EFF7022} = {4A0DFFF5-7BB2-481A-BB0F-152D89823F48}
{7CFEE412-6D6A-407B-9FAE-1640F74D98E7} = {C51CA370-A0C7-4229-90FA-ADF7B51DE021}
{C535C043-7A0E-4F17-89E4-493E7805CFAC} = {4A0DFFF5-7BB2-481A-BB0F-152D89823F48}
{D1287B4D-319A-4D7A-BB5B-93C4E8320480} = {C51CA370-A0C7-4229-90FA-ADF7B51DE021}
EndGlobalSection
EndGlobal

5
toc.yml Normal file
View file

@ -0,0 +1,5 @@
- name: Articles
href: articles/
- name: Api Documentation
href: api/
homepage: api/index.md