Skip to content

Commit

Permalink
Add support for namespaces
Browse files Browse the repository at this point in the history
Add Namespace field to definition

Add includeNamespace optional parameter to WithId

Implementation of conditional inclusion of namespace for WithId

Make expression syntax creation local functions
  • Loading branch information
LykaiosNZ committed May 4, 2024
1 parent 0c29154 commit 6cdef36
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 87 deletions.
98 changes: 84 additions & 14 deletions src/SpiceWeaver/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,14 @@ public static NamespaceDeclarationSyntax GenerateSyntax(string @namespace, strin
private static ClassDeclarationSyntax CreateDefinition(Definition definition)
{
var name = ConstStringField("Name", definition.Name);
var withIdMethod = WithIdMethod(definition.Name);
var nameSpace = ConstNullableStringField("Namespace", definition.Namespace);
var withIdMethod = WithIdMethod(definition.Name, definition.Namespace);

var relations = definition.Relations.Select(RelationField).ToArray();
var permissions = definition.Permissions.Select(PermissionField).ToArray();

var definitionClass = StaticClass(definition.Name.ToPascalCase())
.AddMembers(name, withIdMethod);
.AddMembers(name, nameSpace, withIdMethod);

if (relations.Any())
{
Expand Down Expand Up @@ -111,6 +112,22 @@ private static MemberDeclarationSyntax ConstStringField(string name, string valu
FieldDeclaration(InitializedStringVariable(name, value))
.AddModifiers(Public, Const);

private static MemberDeclarationSyntax ConstNullableStringField(string name, string? value) =>
FieldDeclaration(
VariableDeclaration(NullableType(StringType))
.AddVariables(
VariableDeclarator(Identifier(name))
.WithInitializer(
EqualsValueClause(
value is null
? LiteralExpression(SyntaxKind.NullLiteralExpression)
: LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(value))
)
)
)
)
.AddModifiers(Public, Const);

private static VariableDeclarationSyntax InitializedStringVariable(string name, string value) =>
VariableDeclaration(StringType)
.AddVariables(
Expand All @@ -120,32 +137,85 @@ private static VariableDeclarationSyntax InitializedStringVariable(string name,
LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(value))
)));

private static MethodDeclarationSyntax WithIdMethod(string resourceName)
private static MethodDeclarationSyntax WithIdMethod(string resourceName, string? @namespace)
{
const string idParameterIdentifier = "id";
const string includeNamespaceParameterIdentifier = "includeNamespace";

ExpressionSyntax expressionBody = string.IsNullOrWhiteSpace(@namespace)
? ResourceIdInterpolationExpression(resourceName, idParameterIdentifier)
: ConditionallyNamespacedResourceIdExpression(resourceName, @namespace!, idParameterIdentifier,
includeNamespaceParameterIdentifier);

return MethodDeclaration(StringType, Identifier("WithId"))
.AddModifiers(Public, Static)
.AddParameterListParameters(StringParameter(idParameterIdentifier))
.AddParameterListParameters(BoolParameter(includeNamespaceParameterIdentifier, false))
.WithExpressionBody(
ArrowExpressionClause(ResourceIdInterpolationExpression(resourceName, idParameterIdentifier))
ArrowExpressionClause(expressionBody)
)
.WithSemicolonToken(SemiColon);

static InterpolatedStringExpressionSyntax ResourceIdInterpolationExpression(string resourceName,
string idIdentifier) =>
InterpolatedStringExpression(Token(SyntaxKind.InterpolatedStringStartToken))
.AddContents(
InterpolatedStringText(
Token(
TriviaList(), SyntaxKind.InterpolatedStringTextToken, $"{resourceName}:", "", TriviaList()
)
), Interpolation(IdentifierName(idIdentifier))
);

static ConditionalExpressionSyntax ConditionallyNamespacedResourceIdExpression(string resourceName,
string @namespace,
string idIdentifier, string includeNamespaceIdentifier) => ConditionalExpression(
IdentifierName(includeNamespaceIdentifier),
InterpolatedStringExpression(Token(SyntaxKind.InterpolatedStringStartToken))
.AddContents(
InterpolatedStringText(
Token(
TriviaList(), SyntaxKind.InterpolatedStringTextToken, $"{@namespace}/{resourceName}:", "",
TriviaList()
)
),
Interpolation(IdentifierName(idIdentifier))
),
InterpolatedStringExpression(
Token(SyntaxKind.InterpolatedStringStartToken))
.AddContents(
InterpolatedStringText(
Token(
TriviaList(), SyntaxKind.InterpolatedStringTextToken, $"{resourceName}:", "",
TriviaList())
),
Interpolation(IdentifierName(idIdentifier))
)
);
}

private static InterpolatedStringExpressionSyntax ResourceIdInterpolationExpression(string resourceName,
string idIdentifier) =>
InterpolatedStringExpression(Token(SyntaxKind.InterpolatedStringStartToken))
.AddContents(
InterpolatedStringText(
Token(
TriviaList(), SyntaxKind.InterpolatedStringTextToken, $"{resourceName}:", "", TriviaList()
private static ParameterSyntax StringParameter(string identifier) =>
Parameter(Identifier(identifier)).WithType(StringType);

private static ParameterSyntax BoolParameter(string identifier, bool? defaultValue)
{
var parameter = Parameter(Identifier(identifier)).WithType(PredefinedType(Token(SyntaxKind.BoolKeyword)));

if (defaultValue is not null)
{
parameter = parameter.WithDefault(
EqualsValueClause(
LiteralExpression(
defaultValue.Value
? SyntaxKind.TrueLiteralExpression
: SyntaxKind.FalseLiteralExpression
)
), Interpolation(IdentifierName(idIdentifier))
)
);
}

private static ParameterSyntax StringParameter(string identifier) =>
Parameter(Identifier(identifier)).WithType(StringType);
return parameter;
}

private static PredefinedTypeSyntax StringType => PredefinedType(Token(SyntaxKind.StringKeyword));

Expand Down
8 changes: 8 additions & 0 deletions tests/SpiceWeaver.Tests/CodeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public void GenerateFromSchema_ShouldGenerateExpectedOutput()
Snapshot.Match(output);
}

[Test]
public void GenerateFromJson_WhenDefinitionsIncludeNamespaces_ShouldGenerateExpectedOutput()
{
var output = CodeGenerator.Generate("TestNameSpace", "TestSchema", TestSchema.WithNamespacesJson);

Snapshot.Match(output);
}

private static IEnumerable<string?> NullOrWhitespace =>
[
null,
Expand Down
186 changes: 129 additions & 57 deletions tests/SpiceWeaver.Tests/TestSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,146 @@ namespace SpiceWeaver.Tests;
public static class TestSchema
{
public const string SpiceDb = """
definition user {}
definition user {}
definition document {
relation viewer: user
relation editor: user
permission view = viewer + editor
permission edit = editor
}
""";
definition document {
relation viewer: user
relation editor: user
permission view = viewer + editor
permission edit = editor
}
""";

public static readonly Schema Object = JsonConvert.DeserializeObject<Schema>(Json)!;

public const string Json = """
{
"definitions": [
{
"name": "user"
},
{
"name": "document",
"relations": [
{
"definitions": [
{
"name": "user"
},
{
"name": "document",
"relations": [
{
"name": "viewer",
"types": [
{
"type": "user"
}
]
},
{
"name": "editor",
"types": [
{
"type": "user"
}
]
}
],
"permissions": [
{
"name": "view",
"userSet": {
"operation": "union",
"children": [
{
"name": "viewer",
"types": [
{
"type": "user"
}
]
"relation": "viewer"
},
{
"name": "editor",
"types": [
{
"type": "user"
}
]
"relation": "editor"
}
],
"permissions": [
{
"name": "view",
"userSet": {
"operation": "union",
"children": [
{
"relation": "viewer"
},
{
"relation": "editor"
}
]
}
},
]
}
},
{
"name": "edit",
"userSet": {
"operation": "union",
"children": [
{
"name": "edit",
"userSet": {
"operation": "union",
"children": [
{
"relation": "editor"
}
]
}
"relation": "editor"
}
]
}
]
}
""";
}
]
}
]
}
""";

public const string WithNamespaces = """
definition mynamespace/user {}
definition mynamespace/document {
relation viewer: user
relation editor: user
permission view = viewer + editor
permission edit = editor
}
""";

public const string WithNamespacesJson = """
{
"definitions": [
{
"name": "user",
"namespace": "mynamespace"
},
{
"name": "document",
"namespace": "mynamespace",
"relations": [
{
"name": "viewer",
"types": [
{
"type": "user"
}
]
},
{
"name": "editor",
"types": [
{
"type": "user"
}
]
}
],
"permissions": [
{
"name": "view",
"userSet": {
"operation": "union",
"children": [
{
"relation": "viewer"
},
{
"relation": "editor"
}
]
}
},
{
"name": "edit",
"userSet": {
"operation": "union",
"children": [
{
"relation": "editor"
}
]
}
}
]
}
]
}
""";
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
public static class User
{
public const string Name = "user";
public static string WithId(string id) => $"user:{id}";
public const string? Namespace = null;
public static string WithId(string id, bool includeNamespace = false) => $"user:{id}";
}

public static class Document
{
public const string Name = "document";
public static string WithId(string id) => $"document:{id}";
public const string? Namespace = null;
public static string WithId(string id, bool includeNamespace = false) => $"document:{id}";
public static class Relations
{
public const string Viewer = "viewer";
Expand Down
Loading

0 comments on commit 6cdef36

Please sign in to comment.