Skip to content

Commit

Permalink
Adding keep-candid-case option (#51)
Browse files Browse the repository at this point in the history
* Adding keep-candid-case option

Fixes #41

* Fixing tests
  • Loading branch information
Gekctek authored Mar 20, 2023
1 parent 5eca653 commit b8c380c
Show file tree
Hide file tree
Showing 29 changed files with 31,021 additions and 58 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ candid-client-generator gen ./
- `no-folders` - (Bool) OPTIONAL. If true, no sub-folders will be generated for the clients or the models within the clients. All generated files will be in a flat structure. Defaults to false
- `url` - (Text) OPTIONAL. Sets the boundry node url to use for making calls to canisters on the IC. Can be set to a local developer instance/localhost. Defaults to 'https://ic0.app/'. This setting is only useful for clients of generation type `canister`
- `feature-nullable` - (Bool) Optional. Sets whether to use the C# nullable feature when generating the client (like `object?`). Defaults to true
- `keep-candid-case` - (Bool) Optional. If true, the names of properties and methods will keep the raw candid name. Otherwise they will be converted to something prettier. Defaults to false

#### Client Level:

Expand All @@ -355,6 +356,9 @@ candid-client-generator gen ./
- `cansiter-id` - (Text) REQUIRED. The principal id of the canister to generate a client for
- `output-directory` - (Text) OPTIONAL. Directory to put all generated client files. Overrides the top level `output-directory`. NOTE: this does not create a sub-folder based on the client name like the top level `output-directory` does
- `no-folders` - (Bool) OPTIONAL. If true, no sub-folders will be generated for the client. All generated files will be in a flat structure. Defaults to false. Overrides the top level `no-folders`
- `feature-nullable` - (Bool) Optional. Sets whether to use the C# nullable feature when generating the client (like `object?`). Defaults to true. Overrides the top level `feature-nullable`
- `keep-candid-case` - (Bool) Optional. If true, the names of properties and methods will keep the raw candid name. Otherwise they will be converted to something prettier. Defaults to false. Overrides the top level `keep-candid-case`


# Internet Identity

Expand Down
21 changes: 8 additions & 13 deletions samples/Sample.Shared/Address/Address.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
using EdjCase.ICP.Candid.Mapping;
using EdjCase.ICP.Candid.Models;

namespace Sample.Shared.AddressBook
{
public class Address
{
[CandidName("street")]
public string Street { get; set; }
public string street { get; set; }

[CandidName("city")]
public string City { get; set; }
public string city { get; set; }

[CandidName("zip_code")]
public UnboundedUInt ZipCode { get; set; }
public UnboundedUInt zip_code { get; set; }

[CandidName("country")]
public string Country { get; set; }
public string country { get; set; }

public Address(string street, string city, UnboundedUInt zipCode, string country)
{
this.Street = street;
this.City = city;
this.ZipCode = zipCode;
this.Country = country;
this.street = street;
this.city = city;
this.zip_code = zipCode;
this.country = country;
}

public Address()
Expand Down
4 changes: 2 additions & 2 deletions samples/Sample.Shared/Address/AddressBookApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public AddressBookApiClient(IAgent agent, Principal canisterId, CandidConverter?
this.Converter = converter;
}

public async Task SetAddress(string name, Address addr)
public async Task set_address(string name, Address addr)
{
CandidArg arg = CandidArg.FromCandid(CandidTypedValue.FromObject(name), CandidTypedValue.FromObject(addr));
CandidArg reply = await this.Agent.CallAndWaitAsync(this.CanisterId, "set_address", arg);
}

public async System.Threading.Tasks.Task<OptionalValue<Address>> GetAddress(string name)
public async System.Threading.Tasks.Task<OptionalValue<Address>> get_address(string name)
{
CandidArg arg = CandidArg.FromCandid(CandidTypedValue.FromObject(name));
QueryResponse response = await this.Agent.QueryAsync(this.CanisterId, "get_address", arg);
Expand Down
3 changes: 2 additions & 1 deletion samples/Sample.Shared/candid-client.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ name = "AddressBook"
type = "file"
file-path = "../ServiceDefinitionFiles/AddressBook.did"
output-directory = "C:/Git/ICP.NET/samples/Sample.Shared/Address" # override
no-folders = true
no-folders = true
keep-candid-case = true
16 changes: 13 additions & 3 deletions src/ClientGenerator/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
- [GenerateClientFromCanisterAsync(canisterId,options,httpBoundryNodeUrl)](#M-EdjCase-ICP-ClientGenerator-ClientCodeGenerator-GenerateClientFromCanisterAsync-EdjCase-ICP-Candid-Models-Principal,EdjCase-ICP-ClientGenerator-ClientGenerationOptions,System-Uri- 'EdjCase.ICP.ClientGenerator.ClientCodeGenerator.GenerateClientFromCanisterAsync(EdjCase.ICP.Candid.Models.Principal,EdjCase.ICP.ClientGenerator.ClientGenerationOptions,System.Uri)')
- [GenerateClientFromFile(fileText,options)](#M-EdjCase-ICP-ClientGenerator-ClientCodeGenerator-GenerateClientFromFile-System-String,EdjCase-ICP-ClientGenerator-ClientGenerationOptions- 'EdjCase.ICP.ClientGenerator.ClientCodeGenerator.GenerateClientFromFile(System.String,EdjCase.ICP.ClientGenerator.ClientGenerationOptions)')
- [ClientGenerationOptions](#T-EdjCase-ICP-ClientGenerator-ClientGenerationOptions 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions')
- [#ctor(name,namespace,noFolders,featureNullable)](#M-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-#ctor-System-String,System-String,System-Boolean,System-Boolean- 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.#ctor(System.String,System.String,System.Boolean,System.Boolean)')
- [#ctor(name,namespace,noFolders,featureNullable,keepCandidCase)](#M-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-#ctor-System-String,System-String,System-Boolean,System-Boolean,System-Boolean- 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.#ctor(System.String,System.String,System.Boolean,System.Boolean,System.Boolean)')
- [FeatureNullable](#P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-FeatureNullable 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.FeatureNullable')
- [KeepCandidCase](#P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-KeepCandidCase 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.KeepCandidCase')
- [Name](#P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-Name 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.Name')
- [Namespace](#P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-Namespace 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.Namespace')
- [NoFolders](#P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-NoFolders 'EdjCase.ICP.ClientGenerator.ClientGenerationOptions.NoFolders')
Expand Down Expand Up @@ -86,8 +87,8 @@ EdjCase.ICP.ClientGenerator

Options for generating a client

<a name='M-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-#ctor-System-String,System-String,System-Boolean,System-Boolean-'></a>
### #ctor(name,namespace,noFolders,featureNullable) `constructor`
<a name='M-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-#ctor-System-String,System-String,System-Boolean,System-Boolean,System-Boolean-'></a>
### #ctor(name,namespace,noFolders,featureNullable,keepCandidCase) `constructor`

##### Parameters

Expand All @@ -97,6 +98,7 @@ Options for generating a client
| namespace | [System.String](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.String 'System.String') | The base namespace to use in the generated files |
| noFolders | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, there will be no folders, all files will be in the same directory |
| featureNullable | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the nullable C# feature will be used |
| keepCandidCase | [System.Boolean](http://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k:System.Boolean 'System.Boolean') | If true, the names of properties and methods will keep the raw candid name. Otherwise they will be converted to something prettier |

<a name='P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-FeatureNullable'></a>
### FeatureNullable `property`
Expand All @@ -105,6 +107,14 @@ Options for generating a client

If true, the nullable C# feature will be used

<a name='P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-KeepCandidCase'></a>
### KeepCandidCase `property`

##### Summary

If true, the names of properties and methods will keep the raw candid name.
Otherwise they will be converted to something prettier

<a name='P-EdjCase-ICP-ClientGenerator-ClientGenerationOptions-Name'></a>
### Name `property`

Expand Down
9 changes: 8 additions & 1 deletion src/ClientGenerator/API.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 16 additions & 16 deletions src/ClientGenerator/ClientCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ ClientGenerationOptions options
Dictionary<ValueName, SourceCodeType> declaredTypes = service.DeclaredTypes
.Where(t => t.Value is not CandidServiceType) // avoid duplication service of type
.ToDictionary(
t => ValueName.Default(t.Key.ToString()),
t => ResolveSourceCodeType(t.Value)
t => ValueName.Default(t.Key.ToString(), false),
t => ResolveSourceCodeType(t.Value, options.KeepCandidCase)
);

string modelNamespace = options.NoFolders
Expand All @@ -90,7 +90,7 @@ ClientGenerationOptions options
.Select(t => t.Key)
.ToHashSet();

var typeResolver = new RoslynTypeResolver(modelNamespace, aliases, options.FeatureNullable);
var typeResolver = new RoslynTypeResolver(modelNamespace, aliases, options.FeatureNullable, options.KeepCandidCase);
Dictionary<ValueName, ResolvedType> resolvedTypes = declaredTypes
.ToDictionary(
t => t.Key,
Expand All @@ -111,14 +111,14 @@ ClientGenerationOptions options

string clientName = options.Name + "ApiClient";
TypeName clientTypeName = new(clientName, options.Namespace, prefix: null);
ServiceSourceCodeType serviceSourceType = ResolveService(service.Service);
ServiceSourceCodeType serviceSourceType = ResolveService(service.Service, options.KeepCandidCase);
CompilationUnitSyntax clientSource = RoslynSourceGenerator.GenerateClientSourceCode(clientTypeName, options.Namespace, serviceSourceType, typeResolver, aliasTypes);

// TODO? global using only supported in C# 10+
return new ClientSyntax(clientName, clientSource, typeFiles);
}

private static SourceCodeType ResolveSourceCodeType(CandidType type)
private static SourceCodeType ResolveSourceCodeType(CandidType type, bool keepCandidCase)
{

switch (type)
Expand Down Expand Up @@ -158,12 +158,12 @@ private static SourceCodeType ResolveSourceCodeType(CandidType type)
}
case CandidVectorType v:
{
SourceCodeType innerType = ResolveSourceCodeType(v.InnerType);
SourceCodeType innerType = ResolveSourceCodeType(v.InnerType, keepCandidCase);
return new CompiledTypeSourceCodeType(typeof(List<>), innerType);
}
case CandidOptionalType o:
{
SourceCodeType innerType = ResolveSourceCodeType(o.Value);
SourceCodeType innerType = ResolveSourceCodeType(o.Value, keepCandidCase);

return new CompiledTypeSourceCodeType(typeof(OptionalValue<>), innerType);
}
Expand All @@ -173,8 +173,8 @@ private static SourceCodeType ResolveSourceCodeType(CandidType type)
.Select(f =>
{
CandidType fCandidType = f.Value;
SourceCodeType fType = ResolveSourceCodeType(fCandidType);
return (ValueName.Default(f.Key), fType);
SourceCodeType fType = ResolveSourceCodeType(fCandidType, keepCandidCase);
return (ValueName.Default(f.Key, keepCandidCase), fType);
})
.Where(f => f.Item2 != null)
.ToList()!;
Expand All @@ -188,8 +188,8 @@ private static SourceCodeType ResolveSourceCodeType(CandidType type)
// If type is null, then just be a typeless variant
SourceCodeType? sourceCodeType = f.Value == CandidType.Null()
? null
: ResolveSourceCodeType(f.Value);
return (ValueName.Default(f.Key), sourceCodeType);
: ResolveSourceCodeType(f.Value, keepCandidCase);
return (ValueName.Default(f.Key, keepCandidCase), sourceCodeType);
})
.ToList();
return new VariantSourceCodeType(fields);
Expand All @@ -199,15 +199,15 @@ private static SourceCodeType ResolveSourceCodeType(CandidType type)
}
}

internal static ServiceSourceCodeType ResolveService(CandidServiceType s)
internal static ServiceSourceCodeType ResolveService(CandidServiceType s, bool keepCandidCase)
{
List<(ValueName Name, ServiceSourceCodeType.Func FuncInfo)> methods = s.Methods
.Select(m => (ValueName.Default(m.Key.ToString()), ResolveFunc(m.Value)))
.Select(m => (ValueName.Default(m.Key.ToString(), keepCandidCase), ResolveFunc(m.Value, keepCandidCase)))
.ToList();
return new ServiceSourceCodeType(methods);
}

private static ServiceSourceCodeType.Func ResolveFunc(CandidFuncType value)
private static ServiceSourceCodeType.Func ResolveFunc(CandidFuncType value, bool keepCandidCase)
{
List<(ValueName Name, SourceCodeType Type)> argTypes = value.ArgTypes
.Select(ResolveXType)
Expand All @@ -222,8 +222,8 @@ private static ServiceSourceCodeType.Func ResolveFunc(CandidFuncType value)

(ValueName Name, SourceCodeType Type) ResolveXType((CandidId? Name, CandidType Type) a, int i)
{
ValueName name = ValueName.Default(a.Name?.ToString() ?? "arg" + i);
SourceCodeType type = ResolveSourceCodeType(a.Type);
ValueName name = ValueName.Default(a.Name?.ToString() ?? "arg" + i, keepCandidCase);
SourceCodeType type = ResolveSourceCodeType(a.Type, keepCandidCase);
return (name, type);
}
}
Expand Down
12 changes: 11 additions & 1 deletion src/ClientGenerator/ClientGenerationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,25 @@ public class ClientGenerationOptions
/// If true, the nullable C# feature will be used
/// </summary>
public bool FeatureNullable { get; }
/// <summary>
/// If true, the names of properties and methods will keep the raw candid name.
/// Otherwise they will be converted to something prettier
/// </summary>
public bool KeepCandidCase { get; }



/// <param name="name">The name of the client class and file to use</param>
/// <param name="namespace">The base namespace to use in the generated files</param>
/// <param name="noFolders">If true, there will be no folders, all files will be in the same directory</param>
/// <param name="featureNullable">If true, the nullable C# feature will be used</param>
/// <param name="keepCandidCase">If true, the names of properties and methods will keep the raw candid name. Otherwise they will be converted to something prettier</param>
public ClientGenerationOptions(
string name,
string @namespace,
bool noFolders,
bool featureNullable)
bool featureNullable,
bool keepCandidCase)
{
if (string.IsNullOrWhiteSpace(name))
{
Expand All @@ -48,6 +57,7 @@ public ClientGenerationOptions(
this.Namespace = @namespace ?? throw new ArgumentNullException(nameof(@namespace));
this.NoFolders = noFolders;
this.FeatureNullable = featureNullable;
this.KeepCandidCase = keepCandidCase;
}
}
}
10 changes: 6 additions & 4 deletions src/ClientGenerator/PropertyName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ public override int GetHashCode()
return this.CandidName.GetHashCode();
}

public static ValueName Default(CandidTag value)
public static ValueName Default(CandidTag value, bool keepCandidCase)
{
return Default(value.Name ?? value.Id.ToString());
return Default(value.Name ?? value.Id.ToString(), keepCandidCase);
}
public static ValueName Default(string value)
public static ValueName Default(string value, bool keepCandidCase)
{
bool isQuoted = value.StartsWith("\"") && value.EndsWith("\"");
if (isQuoted)
Expand All @@ -140,7 +140,9 @@ public static ValueName Default(string value)
// If Its a number, prefix it
value = "F" + value;
}
string propertyName = StringUtil.ToPascalCase(value);
string propertyName = !keepCandidCase
? StringUtil.ToPascalCase(value)
: value;
string variableName = StringUtil.ToCamelCase(value);
if (IsKeyword(propertyName))
{
Expand Down
Loading

0 comments on commit b8c380c

Please sign in to comment.