mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-06-28 16:58:39 -04:00
a91be36596
# Description of Changes Adds primary keys to procedural views in C#. See for #5111 for the equivalent feature in rust and C# as well as a more detailed description. # API and ABI breaking changes None # Expected complexity level and risk 3 # Testing - [x] Equivalent tests as were added in #5111 for rust and typescript
365 lines
14 KiB
C#
365 lines
14 KiB
C#
namespace SpacetimeDB.Codegen;
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
|
internal static class ErrorDescriptor
|
|
{
|
|
private static readonly ErrorDescriptorGroup group = new("STDB", "SpacetimeDB");
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ReducerReturnType =
|
|
new(
|
|
group,
|
|
"[SpacetimeDB.Reducer] methods must return void",
|
|
method =>
|
|
$"Reducer method {method.Identifier} returns {method.ReturnType} instead of void.",
|
|
method => method.ReturnType
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> AutoIncNotInteger =
|
|
new(
|
|
group,
|
|
"AutoInc fields must be of integer type",
|
|
field =>
|
|
$"Field {field.Name} is marked as AutoInc but it has a non-integer type {field.Type}.",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> UniqueNotEquatable =
|
|
new(
|
|
group,
|
|
"Unique fields must be equatable",
|
|
field =>
|
|
$"Field {field.Name} is marked as Unique but it has a type {field.Type} which is not an equatable primitive.",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<AttributeData> EmptyIndexColumns =
|
|
new(
|
|
group,
|
|
"Index attribute must specify Columns",
|
|
_ => $"Index attribute doesn't specify columns.",
|
|
attr => attr
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<TypeDeclarationSyntax> InvalidTableVisibility =
|
|
new(
|
|
group,
|
|
"Table row visibility must be public or internal",
|
|
table => $"Table {table.Identifier} and its parent types must be public or internal.",
|
|
table => table.Identifier
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<TypeDeclarationSyntax> TableTaggedEnum =
|
|
new(
|
|
group,
|
|
"Tables cannot be tagged enums",
|
|
table => $"Table {table.Identifier} is a tagged enum, which is not allowed.",
|
|
table => table.BaseList!
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
string kind,
|
|
string exportName,
|
|
IEnumerable<string> fullNames
|
|
)> DuplicateExport =
|
|
new(
|
|
group,
|
|
"Duplicate exports",
|
|
ctx =>
|
|
$"{ctx.kind} with the same export name {ctx.exportName} is registered in multiple places: {string.Join(", ", ctx.fullNames)}",
|
|
ctx => Location.None
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ReducerContextParam =
|
|
new(
|
|
group,
|
|
"Reducers must have a first argument of type ReducerContext",
|
|
method =>
|
|
$"Reducer method {method.Identifier} does not have a ReducerContext parameter.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ProcedureContextParam =
|
|
new(
|
|
group,
|
|
"Procedures must have a first argument of type ProcedureContext",
|
|
method =>
|
|
$"Procedure method {method.Identifier} does not have a ProcedureContext parameter.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
MethodDeclarationSyntax method,
|
|
string prefix
|
|
)> ReducerReservedPrefix =
|
|
new(
|
|
group,
|
|
"Reducer method has a reserved name prefix",
|
|
ctx =>
|
|
$"Reducer method {ctx.method.Identifier} starts with '{ctx.prefix}', which is a reserved prefix.",
|
|
ctx => ctx.method.Identifier
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
MethodDeclarationSyntax method,
|
|
string prefix
|
|
)> ProcedureReservedPrefix =
|
|
new(
|
|
group,
|
|
"Procedure method has a reserved name prefix",
|
|
ctx =>
|
|
$"Procedure method {ctx.method.Identifier} starts with '{ctx.prefix}', which is a reserved prefix.",
|
|
ctx => ctx.method.Identifier
|
|
);
|
|
|
|
public static readonly UnusedErrorDescriptor IncompatibleTableSchedule = new(group);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
ReducerKind kind,
|
|
IEnumerable<string> fullNames
|
|
)> DuplicateSpecialReducer =
|
|
new(
|
|
group,
|
|
"Multiple reducers of the same kind",
|
|
ctx =>
|
|
$"Several reducers are assigned to the same lifecycle kind {ctx.kind}: {string.Join(", ", ctx.fullNames)}",
|
|
ctx => Location.None
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
AttributeData attr,
|
|
string message
|
|
)> InvalidScheduledDeclaration =
|
|
new(group, "Invalid scheduled table declaration", ctx => $"{ctx.message}", ctx => ctx.attr);
|
|
|
|
public static readonly ErrorDescriptor<AttributeData> UnexpectedIndexColumns =
|
|
new(
|
|
group,
|
|
"Index attribute on a field must not specify Columns",
|
|
_ =>
|
|
$"Index attribute on a field applies directly to that field, so it doesn't accept the Columns parameter.",
|
|
attr => attr
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
AttributeData attr,
|
|
string columnName,
|
|
string typeName
|
|
)> UnknownColumn =
|
|
new(
|
|
group,
|
|
"Unknown column",
|
|
ctx => $"Could not find the specified column {ctx.columnName} in {ctx.typeName}.",
|
|
ctx => ctx.attr
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> ClientVisibilityNotFilter =
|
|
new(
|
|
group,
|
|
"ClientVisibilityFilters must be Filters",
|
|
field =>
|
|
$"Field {field.Name} is marked as ClientVisibilityFilter but it has type {field.Type} which is not SpacetimeDB.Filter",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> ClientVisibilityNotPublicStaticReadonly =
|
|
new(
|
|
group,
|
|
"ClientVisibilityFilters must be public static readonly",
|
|
field =>
|
|
$"Field {field.Name} is marked as [ClientVisibilityFilter] but it is not public static readonly",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> IncompatibleDefaultAttributesCombination =
|
|
new(
|
|
group,
|
|
"Invalid Combination: AutoInc, Unique or PrimaryKey cannot have a Default value",
|
|
field =>
|
|
$"Field {field.Name} contains a default value and has a AutoInc, Unique or PrimaryKey attributes, which is not allowed.",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> InvalidDefaultValueType =
|
|
new(
|
|
group,
|
|
"Invalid Default Value Type",
|
|
field => $"Default value for field {field.Name} cannot be converted to provided type",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> InvalidDefaultValueFormat =
|
|
new(
|
|
group,
|
|
"Invalid Default Value Format",
|
|
field => $"Default value for field {field.Name} has invalid format for provided type ",
|
|
field => field
|
|
);
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewContextParam =
|
|
new(
|
|
group,
|
|
"Views must start with ViewContext or AnonymousViewContext",
|
|
method =>
|
|
$"View method {method.Identifier} must have a first parameter of type ViewContext or AnonymousViewContext.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewMustHaveName =
|
|
new(
|
|
group,
|
|
"Views must have an explicit name.",
|
|
method => $"View '{method.Identifier}' must have an explicit name.",
|
|
method => method
|
|
);
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewInvalidReturn =
|
|
new(
|
|
group,
|
|
"Views must return T?, List<T>, IQuery<T>, or IEnumerable<T>",
|
|
method =>
|
|
$"View '{method.Identifier}' must return T?, List<T>, IQuery<T>, or IEnumerable<T>.",
|
|
method => method.ReturnType
|
|
);
|
|
|
|
// TODO: Remove once Views support Private: Views must be Public currently
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewMustBePublic =
|
|
new(
|
|
group,
|
|
"Views must be public",
|
|
method =>
|
|
$"View '{method.Identifier}' must have Public = true. Views are always public in SpacetimeDB.",
|
|
method => method
|
|
);
|
|
|
|
// TODO: Remove once Views support arguments: Views must have no arguments beyond the context.
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> ViewArgsUnsupported =
|
|
new(
|
|
group,
|
|
"Views must have no arguments beyond the context.",
|
|
method =>
|
|
$"View '{method.Identifier}' must have no arguments beyond the context. This is a temporary limitation.",
|
|
method => method
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IFieldSymbol> SettingsMustBeConstCaseConversionPolicy =
|
|
new(
|
|
group,
|
|
"[SpacetimeDB.Settings] field must be a const CaseConversionPolicy",
|
|
field =>
|
|
$"Settings field {field.Name} must be declared as 'public const SpacetimeDB.CaseConversionPolicy ...'.",
|
|
field => field
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IEnumerable<string>> DuplicateSettings =
|
|
new(
|
|
group,
|
|
"Multiple [SpacetimeDB.Settings] declarations",
|
|
fullNames =>
|
|
$"[SpacetimeDB.Settings] is declared multiple times: {string.Join(", ", fullNames)}",
|
|
_ => Location.None
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<AttributeData> TableLevelIndexMissingAccessor =
|
|
new(
|
|
group,
|
|
"Table-level index attributes must specify Accessor",
|
|
_ =>
|
|
$"Index attribute on a table declaration must specify Accessor. Field-level index attributes may omit Accessor and default to the field name.",
|
|
attr => attr
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> HttpHandlerContextParam =
|
|
new(
|
|
group,
|
|
"HTTP handlers must have a first argument of type HandlerContext",
|
|
method =>
|
|
$"HTTP handler method {method.Identifier} does not have a HandlerContext parameter.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> HttpHandlerRequestParam =
|
|
new(
|
|
group,
|
|
"HTTP handlers must have a second argument of type HttpRequest",
|
|
method =>
|
|
$"HTTP handler method {method.Identifier} does not have an HttpRequest parameter.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> HttpHandlerReturnType =
|
|
new(
|
|
group,
|
|
"HTTP handlers must return HttpResponse",
|
|
method =>
|
|
$"HTTP handler method {method.Identifier} returns {method.ReturnType} instead of HttpResponse.",
|
|
method => method.ReturnType
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> HttpRouterSignature =
|
|
new(
|
|
group,
|
|
"HTTP routers must be static parameterless methods returning Router",
|
|
method =>
|
|
$"HTTP router method {method.Identifier} must be static, parameterless, and return Router.",
|
|
method => method
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
MethodDeclarationSyntax method,
|
|
string prefix
|
|
)> HttpHandlerReservedPrefix =
|
|
new(
|
|
group,
|
|
"HTTP handler method has a reserved name prefix",
|
|
ctx =>
|
|
$"HTTP handler method {ctx.method.Identifier} starts with '{ctx.prefix}', which is a reserved prefix.",
|
|
ctx => ctx.method.Identifier
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<IEnumerable<string>> DuplicateHttpRouters =
|
|
new(
|
|
group,
|
|
"Multiple [SpacetimeDB.HttpRouter] declarations",
|
|
fullNames =>
|
|
$"[SpacetimeDB.HttpRouter] is declared multiple times: {string.Join(", ", fullNames)}",
|
|
_ => Location.None
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<MethodDeclarationSyntax> HttpHandlerSignature =
|
|
new(
|
|
group,
|
|
"HTTP handlers must be non-generic methods with exactly two parameters",
|
|
method =>
|
|
$"HTTP handler method {method.Identifier} must be non-generic and take exactly two parameters.",
|
|
method => method.ParameterList
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
MethodDeclarationSyntax method,
|
|
SyntaxNode primaryKeySyntax,
|
|
string primaryKey,
|
|
string rowType
|
|
)> ViewPrimaryKeyColumnNotFound =
|
|
new(
|
|
group,
|
|
"View primary key column not found",
|
|
ctx =>
|
|
$"View '{ctx.method.Identifier}' declares primary key '{ctx.primaryKey}', but row type '{ctx.rowType}' does not have a field with that source name.",
|
|
ctx => ctx.primaryKeySyntax
|
|
);
|
|
|
|
public static readonly ErrorDescriptor<(
|
|
MethodDeclarationSyntax method,
|
|
SyntaxNode primaryKeySyntax,
|
|
string primaryKey,
|
|
string type
|
|
)> ViewPrimaryKeyNotFilterable =
|
|
new(
|
|
group,
|
|
"View primary key column type is not supported",
|
|
ctx =>
|
|
$"View '{ctx.method.Identifier}' declares primary key '{ctx.primaryKey}', but its type '{ctx.type}' is not supported for view primary keys.",
|
|
ctx => ctx.primaryKeySyntax
|
|
);
|
|
}
|