Skip to content
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

Add support for namespaces #31

Open
wants to merge 1 commit into
base: main
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
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