Skip to content

[Generator] Add mapping configurations #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/NetCoreForce.ModelGenerator/GenConfig.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using NetCoreForce.Client.Models;

Expand All @@ -12,8 +13,25 @@ public class GenConfig
public string ClassPrefix { get; set; }
public string ClassSuffix { get; set; }
public string ClassNamespace { get; set; }

/// <summary>
/// Add <see cref="NetCoreForce.Models"/> namespace using
/// </summary>
public bool AddNetCoreForceModelsUsings { get; set; }
public bool IncludeCustom { get; set; }
public bool IncludeReferences { get; set; }


public IDictionary<string, ObjectMappingConfig> Mappings { get; set; } = new Dictionary<string, ObjectMappingConfig>(StringComparer.OrdinalIgnoreCase);

public ObjectMappingConfig Mapping(string objectName)
{
ObjectMappingConfig objectMapping = null;

Mappings?.TryGetValue(objectName, out objectMapping);

return objectMapping ?? new ObjectMappingConfig();
}

public GenConfig()
{
Expand Down
88 changes: 88 additions & 0 deletions src/NetCoreForce.ModelGenerator/MappingConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace NetCoreForce.ModelGenerator
{
/// <summary>
/// Mapping configuration
/// </summary>
public class ObjectMappingConfig
{
/// <summary>
/// Used to override the class name (or <c>null</c>).
/// </summary>
public string Name { get; set; }

/// <summary>
/// If <c>true</c>, don't generate the class for this object type (and the relationship to this type too).
/// </summary>
public bool Ignore { get; set; }

/// <summary>
/// The mappings for the fields of this object type.
/// </summary>
public Dictionary<string, FieldMappingConfig> Fields { get; set; } = new Dictionary<string, FieldMappingConfig>();


private List<RegexFieldMappingConfig> _regexFieldsMappings = null;
private List<RegexFieldMappingConfig> RegexFieldMappings
{
get
{
if (_regexFieldsMappings == null)
_regexFieldsMappings = Fields.Select(kvp => new RegexFieldMappingConfig(kvp.Key, kvp.Value)).ToList();

return _regexFieldsMappings;
}
}

/// <summary>
/// Get the <see cref="FieldMappingConfig"/> which it's regex matches the field name given.
/// </summary>
/// <param name="fieldName">The field name</param>
/// <returns>The <see cref="FieldMappingConfig"/> which it's regex matches the field name given or <c>null</c>.</returns>
public FieldMappingConfig Field(string fieldName)
{
FieldMappingConfig mapping = null;

if (!string.IsNullOrWhiteSpace(fieldName))
mapping = RegexFieldMappings.FirstOrDefault(rfb => rfb.IsMatch(fieldName))?.Mapping;

return mapping ?? new FieldMappingConfig();
}

/// <summary>
/// Bind a <see cref="FieldMappingConfig"/> with a <see cref="Regex"/>.
/// </summary>
private class RegexFieldMappingConfig
{
public RegexFieldMappingConfig(string regexPattern, FieldMappingConfig mapping)
{
Regex = new Regex(regexPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
Mapping = mapping;
}

public Regex Regex { get; }
public FieldMappingConfig Mapping { get; }

public bool IsMatch(string fieldName) => Regex.IsMatch(fieldName);
}

/// <summary>
/// Field mapping configuration
/// </summary>
public class FieldMappingConfig
{
/// <summary>
/// Override the field name with this name (if <c>null</c>, keep field name)
/// </summary>
public string Name { get; set; }

/// <summary>
/// Ignore this field
/// </summary>
public bool Ignore { get; set; }
}
}
}
53 changes: 43 additions & 10 deletions src/NetCoreForce.ModelGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,13 @@ private static async Task GenModels(GenConfig config)

Console.WriteLine("Output directory: " + config.OutputDirectory);

if (!Directory.Exists(config.OutputDirectory))
{
Directory.CreateDirectory(config.OutputDirectory);

Console.WriteLine("Output directory created : " + config.OutputDirectory);
}


bool generateAll = false;
if (config.Objects != null && config.Objects.Count > 0)
Expand Down Expand Up @@ -408,7 +415,7 @@ private static async Task GenModels(GenConfig config)
{
if (config.Objects != null && config.Objects.Count > 0)
{
bool incl = config.Objects.Where(o => o.ToLowerInvariant() == obj.Name.ToLowerInvariant()).Count() > 0;
bool incl = config.Objects.Any(o => o.ToLowerInvariant() == obj.Name.ToLowerInvariant());
if (!incl)
{
#if DEBUG
Expand All @@ -423,7 +430,8 @@ private static async Task GenModels(GenConfig config)

Console.Write("Generating model for {0} - ", obj.Name);

string className = obj.Name;
var classMapping = config.Mapping(obj.Name);
var className = classMapping.Name ?? obj.Name;

className = string.Format("{0}{1}{2}", config.ClassPrefix ?? string.Empty, className, config.ClassSuffix ?? string.Empty);

Expand Down Expand Up @@ -463,20 +471,30 @@ public static async Task<string> GenClass(ForceClient client, string objectName,
string newline = Environment.NewLine;

gen.AppendLine("using System;");
gen.AppendLine("using System.CodeDom.Compiler;");
gen.AppendLine("using NetCoreForce.Client.Models;");
gen.AppendLine("using NetCoreForce.Client.Attributes;");

if (config.AddNetCoreForceModelsUsings)
gen.AppendLine("using NetCoreForce.Models;");

gen.AppendLine("using Newtonsoft.Json;");

gen.AppendLine();
if (!string.IsNullOrEmpty(config.ClassNamespace))
{
gen.AppendLine("namespace " + config.ClassNamespace);
gen.AppendLine("{");
}

var assembly = Assembly.GetEntryAssembly().GetName();

gen.AppendLine("\t///<summary>");
gen.AppendLine($"\t/// {WebUtility.HtmlEncode(data.Label)}");
gen.AppendLine($"\t///<para>SObject Name: {data.Name}</para>");
gen.AppendLine($"\t///<para>Custom Object: {data.Custom.ToString()}</para>");
gen.AppendLine("\t///</summary>");
gen.AppendLine($"\t[GeneratedCodeAttribute(\"{typeof(Program).Namespace}\", \"{assembly.Version}\")]");
gen.AppendLine($"\tpublic class {className} : SObject");
gen.AppendLine("\t{");

Expand All @@ -492,14 +510,16 @@ public static async Task<string> GenClass(ForceClient client, string objectName,
// gen.AppendLine("\t\t{}");
// gen.AppendLine();

var classMapping = config.Mapping(objectName);

foreach (var field in data.Fields)
{
try
{
if (field.Custom && !config.IncludeCustom)
{
var fieldMapping = classMapping.Field(field.Name);

if (field.Custom && !config.IncludeCustom || fieldMapping.Ignore)
continue;
}

gen.AppendLine("\t\t///<summary>");
gen.AppendLine("\t\t/// " + WebUtility.HtmlEncode(field.Label));
Expand All @@ -519,7 +539,8 @@ public static async Task<string> GenClass(ForceClient client, string objectName,

gen.AppendLine("\t\t///</summary>");

gen.AppendLine(string.Format("\t\t[JsonProperty(PropertyName = \"{0}\")]", JsonName(field.Name)));
var jsonName = JsonName(field.Name);
gen.AppendLine(string.Format("\t\t[JsonProperty(PropertyName = \"{0}\")]", jsonName));

if (!field.Creatable || !field.Updateable)
{
Expand Down Expand Up @@ -555,12 +576,14 @@ public static async Task<string> GenClass(ForceClient client, string objectName,
csTypeName += "?";
}

gen.AppendLine(string.Format("\t\tpublic {0} {1} {{ get; set; }}", csTypeName, field.Name));
gen.AppendLine(string.Format("\t\tpublic {0} {1} {{ get; set; }}", csTypeName, fieldMapping.Name ?? field.Name));
gen.AppendLine();

if (field.Type == "reference" && config.IncludeReferences)
{
if (string.IsNullOrEmpty(field.RelationshipName) || field.ReferenceTo.Count > 1)
var relationshipfieldMapping = classMapping.Field(field.RelationshipName);

if (string.IsNullOrEmpty(field.RelationshipName) || field.ReferenceTo.Count > 1 || relationshipfieldMapping.Ignore)
{
//only do single-object relationships
continue;
Expand All @@ -572,16 +595,26 @@ public static async Task<string> GenClass(ForceClient client, string objectName,
continue;
}

var targetMapping = config.Mapping(field.ReferenceTo[0]);

if (targetMapping.Ignore) // if we ignore the target class --> ignore the relationship
continue;

string referenceClass = GetPrefixedSuffixed(config, targetMapping.Name ?? field.ReferenceTo[0]);

if (!string.IsNullOrEmpty(config.ClassNamespace) && config.AddNetCoreForceModelsUsings)
referenceClass = config.ClassNamespace + "." + referenceClass;

gen.AppendLine("\t\t///<summary>");
gen.AppendLine("\t\t/// ReferenceTo: " + field.ReferenceTo[0]);
gen.AppendLine("\t\t/// <para>RelationshipName: " + field.RelationshipName + "</para>");
gen.AppendLine("\t\t///</summary>");
gen.AppendLine(string.Format("\t\t[JsonProperty(PropertyName = \"{0}\")]", JsonName(field.RelationshipName)));
gen.AppendLine("\t\t[Updateable(false), Createable(false)]");

string referenceClass = GetPrefixedSuffixed(config, field.ReferenceTo[0]);


gen.AppendLine(string.Format("\t\tpublic {0} {1} {{ get; set; }}", referenceClass, field.RelationshipName));
gen.AppendLine(string.Format("\t\tpublic {0} {1} {{ get; set; }}", referenceClass, relationshipfieldMapping.Name ?? field.RelationshipName));
gen.AppendLine();
}
}
Expand Down
57 changes: 56 additions & 1 deletion src/NetCoreForce.ModelGenerator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,65 @@ No configuration file is required, however you can include the --save-config opt
"ClassSuffix": null,
"ClassNamespace": "NetCoreForce.Models",
"IncludeCustom": true,
"IncludeReferences": true
"IncludeReferences": true,
"AddNetCoreForceModelsUsings": true,
}
```

**Generating all objects at once:** If you wish to generate all queryable objects in the generated output, add "all" as the first or only item in the "Objects" array in the config file, or enter "all" (without quotes) when prompted in the console.

**Note:** if you use the -r/--include-references option, you may run into compile errors with missing classes. For instance, the Salesforce "User" object appears on many objects - if you didnt include this in your list of models to generate, you will either need to include it, or manually remove those properties from the generated classes.


## Mappings configuration

Features :
- Rename a class
- Ignore a class generation (and all its referencing relationships)
- Rename a field in a class
- Ignore a field in a class
- Ignore all fields matching a regex in a class

### Example config file

```json
{
/// ...

//Optional
"Mappings": {

//Rename Object_Name to ObjectName
"object_name": {
"Name": "ObjectName"
},

//Ignore Object_Name_To_Ignore type generation
// (+ ignore all relationships to this Object_Name_To_Ignore)
"Object_Name_To_Ignore": {
"Ignore": true
},

//Configure object fields
"Object_name_config_fields": {
"Fields": {

// rename field during generation
"old_name": {
"Name": "NewName"
},

// ignore field
"field_to_ignore": {
"Ignore": true
},

// ignore multile fields matching a regex (eg: starting with 'billing')
"billing.*": {
"Ignore": true
}
}
}
}

}```
Binary file not shown.