Skip to content

Commit

Permalink
Merge pull request #116 from Sevitas/112_new_class_format
Browse files Browse the repository at this point in the history
112 new class format
  • Loading branch information
Sevitas authored Aug 26, 2021
2 parents a8f1852 + 4f70d65 commit 2828223
Show file tree
Hide file tree
Showing 20 changed files with 1,607 additions and 76 deletions.
28 changes: 19 additions & 9 deletions src/Kentico.Kontent.ModelGenerator.Core/ClassCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using Kentico.Kontent.Management.Modules.ModelBuilders;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -10,6 +11,7 @@ namespace Kentico.Kontent.ModelGenerator.Core
{
public class ClassCodeGenerator
{
private static readonly string KontentElementIdAttributeName = new string(nameof(KontentElementIdAttribute).SkipLast(9).ToArray());
public const string DEFAULT_NAMESPACE = "KenticoKontentModels";

public ClassDefinition ClassDefinition { get; }
Expand All @@ -35,21 +37,19 @@ public string GenerateCode(bool cmApi = false)
{
var cmApiUsings = new[]
{
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName($"{nameof(Newtonsoft)}.{nameof(Newtonsoft.Json)}")),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(Management.Models.Items.ContentItemModel).Namespace!)),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(Management.Models.Assets.AssetModel).Namespace!))
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(Management.Models.LanguageVariants.Elements.BaseElement).Namespace!)),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(KontentElementIdAttribute).Namespace!)),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName($"{nameof(Newtonsoft)}.{nameof(Newtonsoft.Json)}"))
};

var deliveryUsings = new[]
{
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(nameof(System))),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(System.Collections.Generic.IEnumerable<>).Namespace!)),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(Delivery.Abstractions.IApiResponse).Namespace!))
};

var usings = new[]
{
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(nameof(System))),
SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(System.Collections.Generic.IEnumerable<>).Namespace!)),
}.Concat(cmApi ? cmApiUsings : deliveryUsings).ToArray();
var usings = (cmApi ? cmApiUsings : deliveryUsings).ToArray();

MemberDeclarationSyntax[] properties = ClassDefinition.Properties.OrderBy(p => p.Identifier).Select((element) =>
{
Expand All @@ -75,7 +75,17 @@ public string GenerateCode(bool cmApi = false)
SyntaxFactory.AttributeArgument(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal(element.Codename)))))))));
SyntaxFactory.Literal(element.Codename)))))))),
SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName(KontentElementIdAttributeName))
.WithArgumentList(
SyntaxFactory.AttributeArgumentList(
SyntaxFactory.SingletonSeparatedList(
SyntaxFactory.AttributeArgument(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal(element.Id)))))))));
}

return property;
Expand Down
33 changes: 26 additions & 7 deletions src/Kentico.Kontent.ModelGenerator.Core/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,23 @@
using Kentico.Kontent.Delivery.Abstractions;
using Kentico.Kontent.ModelGenerator.Core.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;

namespace Kentico.Kontent.ModelGenerator.Core
{
public class CodeGenerator
{
private readonly CodeGeneratorOptions _options;
private readonly IDeliveryClient _client;
private readonly IManagementClient _managementClient;
private readonly IOutputProvider _outputProvider;

public CodeGenerator(IOptions<CodeGeneratorOptions> options, IDeliveryClient deliveryClient, IOutputProvider outputProvider)
public CodeGenerator(IOptions<CodeGeneratorOptions> options, IDeliveryClient deliveryClient, IOutputProvider outputProvider, IManagementClient managementClient)
{
_options = options.Value;
_client = deliveryClient;
_outputProvider = outputProvider;
_managementClient = managementClient;
}

public async Task<int> RunAsync()
Expand Down Expand Up @@ -85,19 +88,26 @@ internal async Task GenerateTypeProvider()

internal async Task<IEnumerable<ClassCodeGenerator>> GetClassCodeGenerators(bool structuredModel = false)
{
IEnumerable<IContentType> contentTypes = (await _client.GetTypesAsync()).Types;
IEnumerable<IContentType> deliveryTypes = (await _client.GetTypesAsync()).Types;
var managementTypes = await _managementClient.GetAllContentTypesAsync(_options);

var codeGenerators = new List<ClassCodeGenerator>();
if (contentTypes != null)
if (deliveryTypes != null)
{
foreach (var contentType in contentTypes)
foreach (var contentType in deliveryTypes)
{
try
{
if (_options.GeneratePartials)
{
codeGenerators.Add(GetCustomClassCodeGenerator(contentType));
}
codeGenerators.Add(GetClassCodeGenerator(contentType, structuredModel));

var managementContentType = _options.ContentManagementApi
? managementTypes.FirstOrDefault(ct => ct["codename"].ToObject<string>() == contentType.System.Codename)
: null;

codeGenerators.Add(GetClassCodeGenerator(contentType, structuredModel, managementContentType));
}
catch (InvalidIdentifierException)
{
Expand All @@ -108,7 +118,7 @@ internal async Task<IEnumerable<ClassCodeGenerator>> GetClassCodeGenerators(bool
return codeGenerators;
}

internal ClassCodeGenerator GetClassCodeGenerator(IContentType contentType, bool structuredModel)
internal ClassCodeGenerator GetClassCodeGenerator(IContentType contentType, bool structuredModel, JObject managementContentType = null)
{
var classDefinition = new ClassDefinition(contentType.System.Codename);

Expand All @@ -121,7 +131,12 @@ internal ClassCodeGenerator GetClassCodeGenerator(IContentType contentType, bool
{
elementType += Property.STRUCTURED_SUFFIX;
}
var property = Property.FromContentType(element.Codename, elementType, _options.ContentManagementApi);

var elementId = _options.ContentManagementApi
? ContentTypeJObjectHelper.GetElementIdFromContentType(managementContentType, element.Codename)
: null;

var property = Property.FromContentType(element.Codename, elementType, _options.ContentManagementApi, elementId);
classDefinition.AddPropertyCodenameConstant(element);
classDefinition.AddProperty(property);
}
Expand All @@ -133,6 +148,10 @@ internal ClassCodeGenerator GetClassCodeGenerator(IContentType contentType, bool
{
Console.WriteLine($"Warning: Can't create valid C# Identifier from '{element.Codename}'. Skipping element.");
}
catch (InvalidIdException)
{
Console.WriteLine($"Warning: Can't create valid Id for '{element.Codename}'. Skipping element.");
}
catch (ArgumentException)
{
Console.WriteLine($"Warning: Skipping unknown Content Element type '{element.Type}'. (Content Type: '{classDefinition.ClassName}', Element Codename: '{element.Codename}').");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Kentico.Kontent.Delivery.Abstractions;
using Kentico.Kontent.Management;

namespace Kentico.Kontent.ModelGenerator.Core.Configuration
{
Expand All @@ -15,6 +16,11 @@ public class CodeGeneratorOptions
/// </summary>
public DeliveryOptions DeliveryOptions { get; set; }

/// <summary>
/// Management Client configuration.
/// </summary>
public ManagementOptions ManagementOptions { get; set; }

/// <summary>
/// Namespace name of the generated classes
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;

namespace Kentico.Kontent.ModelGenerator.Core
{
public static class ContentTypeJObjectHelper
{
public static string GetElementIdFromContentType(JObject managementContentType, string elementCodename)
{
if (!managementContentType.TryGetValue("elements", out var elements))
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', couldn't find {nameof(elements)}.");
}

if (elements is not { Type: JTokenType.Array })
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', {nameof(elements)} has invalid type.");
}

var element = elements.ToObject<List<JObject>>().FirstOrDefault(el =>
{
if (!el.TryGetValue("codename", out var codename))
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', couldn't find {nameof(codename)}.");
}

if (codename is not { Type: JTokenType.String })
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', {nameof(elements)} has invalid type.");
}

return codename.ToObject<string>() == elementCodename;
});

if (element == null)
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', missing element.");
}

if (!element.TryGetValue("id", out var elementId))
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', couldn't find {nameof(elementId)}.");
}

if (elementId is not { Type: JTokenType.String })
{
throw new InvalidIdException($"Unable to create a valid Id for '{elementCodename}', {nameof(elementId)} has invalid type.");
}

return elementId.ToObject<string>();
}
}
}
12 changes: 12 additions & 0 deletions src/Kentico.Kontent.ModelGenerator.Core/IManagementClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Kentico.Kontent.ModelGenerator.Core.Configuration;
using Newtonsoft.Json.Linq;

namespace Kentico.Kontent.ModelGenerator.Core
{
public interface IManagementClient
{
Task<IList<JObject>> GetAllContentTypesAsync(CodeGeneratorOptions options);
}
}
11 changes: 11 additions & 0 deletions src/Kentico.Kontent.ModelGenerator.Core/InvalidIdException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace Kentico.Kontent.ModelGenerator.Core
{
public class InvalidIdException : Exception
{
public InvalidIdException(string message) : base(message)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Kentico.Kontent.Management" Version="2.1.0" />
<PackageReference Include="Kentico.Kontent.Delivery.Abstractions" Version="14.2.1" />
<PackageReference Include="Kentico.Kontent.Management" Version="3.0.0-alpha2" />
<PackageReference Include="Kentico.Kontent.Delivery.Abstractions" Version="15.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.8.0" />
</ItemGroup>

Expand Down
57 changes: 57 additions & 0 deletions src/Kentico.Kontent.ModelGenerator.Core/ManagementClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Kentico.Kontent.ModelGenerator.Core.Configuration;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Kentico.Kontent.ModelGenerator.Core
{
public class ManagementClient : IManagementClient
{
private const int MilisecondsDelay = 1000;
private readonly HttpClient _httpClient;

public ManagementClient(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<IList<JObject>> GetAllContentTypesAsync(CodeGeneratorOptions options)
{
if (!options.ContentManagementApi)
{
return null;
}

var contentTypes = new List<JObject>();
string continuationToken = null;
do
{
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ManagementOptions.ApiKey);
if (continuationToken != null)
{
_httpClient.DefaultRequestHeaders.Add("x-continuation", continuationToken);
}

var response = await _httpClient.GetAsync(new Uri($"https://manage.kontent.ai/v2/projects/{options.ManagementOptions.ProjectId}/types"), HttpCompletionOption.ResponseContentRead);

var responseStream = await response.Content.ReadAsStreamAsync();
var responseObject = await JObject.ReadFromAsync(new JsonTextReader(new StreamReader(responseStream)));

continuationToken = responseObject["pagination"]["continuation_token"].ToObject<string>();

contentTypes.AddRange(responseObject["types"].ToObject<List<JObject>>());

await Task.Delay(MilisecondsDelay);
}
while (continuationToken != null);

return contentTypes;
}
}
}
29 changes: 16 additions & 13 deletions src/Kentico.Kontent.ModelGenerator.Core/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class Property

public string Codename { get; }

public string Id { get; }

/// <summary>
/// Returns return type of the property in a string format (e.g.: "string").
/// </summary>
Expand All @@ -36,40 +38,41 @@ public class Property

private static readonly Dictionary<string, string> ContentManagementTypes = new Dictionary<string, string>
{
{ "text", "string" },
{ "rich_text", "string" },
{ "number", "decimal?" },
{ "multiple_choice", "IEnumerable<MultipleChoiceOptionIdentifier>" },
{ "date_time", "DateTime?" },
{ "asset", "IEnumerable<AssetIdentifier>" },
{ "modular_content", "IEnumerable<ContentItemIdentifier>" },
{ "taxonomy", "IEnumerable<TaxonomyTermIdentifier>" },
{ "url_slug", "string" },
{ "custom", "string" }
{ "text", "TextElement" },
{ "rich_text", "RichTextElement" },
{ "number", "NumberElement" },
{ "multiple_choice", "MultipleChoiceElement" },
{ "date_time", "DateTimeElement"},
{ "asset", "AssetElement" },
{ "modular_content", "LinkedItemsElement" },
{ "taxonomy", "TaxonomyElement" },
{ "url_slug", "UrlSlugElement" },
{ "custom", "CustomElement" }
};

private static Dictionary<string, string> ContentTypeToTypeName(bool cmApi)
=> cmApi ? ContentManagementTypes : DeliverTypes;

public Property(string codename, string typeName)
public Property(string codename, string typeName, string id = null)
{
Codename = codename;
TypeName = typeName;
Id = id;
}

public static bool IsContentTypeSupported(string contentType, bool cmApi = false)
{
return ContentTypeToTypeName(cmApi).ContainsKey(contentType);
}

public static Property FromContentType(string codename, string contentType, bool cmApi = false)
public static Property FromContentType(string codename, string contentType, bool cmApi = false, string id = null)
{
if (!IsContentTypeSupported(contentType, cmApi))
{
throw new ArgumentException($"Unknown Content Type {contentType}", nameof(contentType));
}

return new Property(codename, ContentTypeToTypeName(cmApi)[contentType]);
return new Property(codename, ContentTypeToTypeName(cmApi)[contentType], id);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Kentico.Kontent.Delivery" Version="14.2.1" />
<PackageReference Include="Kentico.Kontent.Delivery" Version="15.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
Expand Down
Loading

0 comments on commit 2828223

Please sign in to comment.