Skip to content
Merged
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
3 changes: 3 additions & 0 deletions generators/csharp/base/src/context/CsharpTypeMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ export class CsharpTypeMapper extends WithGeneration {
}
return objectClassReference;
}
if (this.context.protobufResolver.isExternalProtobufType(named.typeId)) {
return this.context.protobufResolver.getExternalProtobufClassReference(named.typeId);
}

const typeDeclaration = this.model.dereferenceType(named.typeId).typeDeclaration;
switch (typeDeclaration.shape.type) {
Expand Down
15 changes: 14 additions & 1 deletion generators/csharp/base/src/project/CsharpProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,8 @@ ${this.getAdditionalItemGroups().join(`\n${this.generation.constants.formatting.

result.push("");
result.push("<ItemGroup>");
result.push(' <PackageReference Include="Google.Protobuf" Version="3.27.2" />');
result.push(' <PackageReference Include="Google.Api.CommonProtos" Version="2.17.0" />');
result.push(' <PackageReference Include="Google.Protobuf" Version="3.31.1" />');
result.push(' <PackageReference Include="Grpc.Net.Client" Version="2.63.0" />');
result.push(' <PackageReference Include="Grpc.Net.ClientFactory" Version="2.63.0" />');
result.push(' <PackageReference Include="Grpc.Tools" Version="2.64.0">');
Expand All @@ -918,6 +919,11 @@ ${this.getAdditionalItemGroups().join(`\n${this.generation.constants.formatting.

result.push("<ItemGroup>");
for (const protobufSourceFilePath of protobufSourceFilePaths) {
// Skip proto files provided by external packages (e.g. Google.Api.CommonProtos)
// to avoid conflicting with the types from those packages.
if (EXTERNAL_PROTO_FILE_PREFIXES.some((prefix) => protobufSourceFilePath.startsWith(prefix))) {
continue;
}
const protobufSourceWindowsPath = this.relativePathToWindowsPath(protobufSourceFilePath);
result.push(
` <Protobuf Include="${pathToProtobufDirectory}\\${protobufSourceWindowsPath}" GrpcServices="Client" ProtoRoot="${pathToProtobufDirectory}">`
Expand Down Expand Up @@ -1009,3 +1015,10 @@ ${this.getAdditionalItemGroups().join(`\n${this.generation.constants.formatting.
return path.win32.normalize(relativePath);
}
}

/**
* Proto file path prefixes for types provided by external NuGet packages
* (e.g. Google.Api.CommonProtos). These files should be excluded from
* Grpc.Tools compilation to avoid conflicting type definitions.
*/
const EXTERNAL_PROTO_FILE_PREFIXES = ["google/rpc/", "google/api/"];
15 changes: 14 additions & 1 deletion generators/csharp/base/src/proto/CsharpProtobufTypeMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ class ToProtoPropertyMapper extends WithGeneration {
if (this.context.protobufResolver.isWellKnownAnyProtobufType(named.typeId)) {
return this.getValueForAny({ propertyName });
}
if (this.context.protobufResolver.isExternalProtobufType(named.typeId)) {
// External proto types (e.g. google.rpc.Status) are used directly;
// no conversion needed since the SDK type IS the proto type.
return this.csharp.codeblock(propertyName);
}
const resolvedType = this.model.dereferenceType(named.typeId).typeDeclaration;
if (resolvedType.shape.type === "enum") {
const enumClassReference = this.context.csharpTypeMapper.convertToClassReference(named, {
Expand Down Expand Up @@ -668,6 +673,11 @@ class FromProtoPropertyMapper extends WithGeneration {
named: NamedType;
wrapperType?: WrapperType;
}): ast.CodeBlock {
if (this.context.protobufResolver.isExternalProtobufType(named.typeId)) {
// External proto types (e.g. google.rpc.Status) are used directly;
// no conversion needed since the SDK type IS the proto type.
return this.csharp.codeblock(propertyName);
}
const resolvedType = this.model.dereferenceType(named.typeId).typeDeclaration;
if (resolvedType.shape.type === "enum") {
const enumClassReference = this.context.csharpTypeMapper.convertToClassReference(named, {
Expand All @@ -688,9 +698,12 @@ class FromProtoPropertyMapper extends WithGeneration {
propertyName
});
}
const propertyClassReference = this.context.csharpTypeMapper.convertToClassReference(named);
const propertyClassReference = this.context.csharpTypeMapper.convertToClassReference(named, {
fullyQualified: true
});
if (wrapperType === WrapperType.List) {
// The static function is mapped within a LINQ expression.
// Use fully qualified reference to avoid collisions with property names.
return this.csharp.codeblock((writer) => {
writer.writeNode(propertyClassReference);
writer.write(".FromProto");
Expand Down
56 changes: 56 additions & 0 deletions generators/csharp/base/src/proto/ProtobufResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ export class ProtobufResolver extends WithGeneration {
});
}

/**
* Returns true if the type is an external proto type (e.g. google.rpc.Status)
* that should be surfaced directly in the SDK without generating a wrapper type.
*/
public isExternalProtobufType(typeId: TypeId): boolean {
const protobufType = this.getProtobufTypeForTypeId(typeId);
if (protobufType?.type === "userDefined") {
const packageName = protobufType.file.packageName;
if (packageName != null && EXTERNAL_PROTO_PACKAGES.has(packageName)) {
return true;
}
}
// Fallback: match on the type's declared name for cases where
// proto source may not be fully resolved.
const typeDeclaration = this.generation.ir.types[typeId];
const typeName = typeDeclaration?.name?.name?.originalName;
return typeName != null && EXTERNAL_PROTO_TYPE_NAMES.has(typeName);
}

/**
* Returns the class reference for an external proto type, using
* known mappings to resolve the correct namespace and type name.
*/
public getExternalProtobufClassReference(typeId: TypeId): ast.ClassReference {
const typeDeclaration = this.generation.ir.types[typeId];
const typeName = typeDeclaration?.name?.name?.originalName;
const mapping = EXTERNAL_PROTO_TYPE_CLASS_REFERENCES[typeName ?? ""];
if (mapping != null) {
return this.csharp.classReference({
name: mapping.name,
namespace: mapping.namespace
});
}
// Fall back to the proto source if no known mapping exists.
return this.getProtobufClassReference(typeId);
}

public isWellKnownAnyProtobufType(typeId: TypeId): boolean {
return this._isWellKnownProtobufType({
typeId,
Expand Down Expand Up @@ -130,3 +167,22 @@ export class ProtobufResolver extends WithGeneration {
return typeDeclaration.source.type === "proto" ? typeDeclaration.source.value : undefined;
}
}

/**
* Proto packages whose types should be surfaced directly in the SDK
* (using the proto-generated class) without generating a separate wrapper type.
*/
const EXTERNAL_PROTO_PACKAGES = new Set(["google.rpc"]);

/**
* Type names that correspond to external proto types. Used as a fallback
* when proto source info may not be fully resolved (e.g. from OpenAPI imports).
*/
const EXTERNAL_PROTO_TYPE_NAMES = new Set(["GoogleRpcStatus"]);

/**
* Known external proto type class references, keyed by IR type name.
*/
const EXTERNAL_PROTO_TYPE_CLASS_REFERENCES: Record<string, { name: string; namespace: string }> = {
GoogleRpcStatus: { name: "Status", namespace: "Google.Rpc" }
};
5 changes: 5 additions & 0 deletions generators/csharp/model/src/generateModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export function generateModels({ context }: { context: ModelGeneratorContext }):
// The well-known Protobuf types are generated separately.
continue;
}
if (context.protobufResolver.isExternalProtobufType(typeId)) {
// External proto types (e.g. google.rpc.Status) are used directly
// without generating a separate SDK wrapper type.
continue;
}
const file = typeDeclaration.shape._visit<CSharpFile | undefined>({
alias: (aliasDeclaration) => {
// Generate literal struct files for named literal alias types when generateLiterals is on.
Expand Down
18 changes: 18 additions & 0 deletions generators/csharp/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 2.30.3
changelogEntry:
- summary: |
Add better support for the `google.rpc.Status` type, and stop
generating redundant wrapper types for `google.api` and
`google.rpc` types.
type: fix
createdAt: "2026-03-13"
irVersion: 65

- version: 2.30.2
changelogEntry:
- summary: |
Fix gRPC code generation to support enums with numeric identifiers.
type: fix
createdAt: "2026-03-13"
irVersion: 65

- version: 2.30.1
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"type": "object",
"properties": {
"resource": {
"oneOf": [
{
"$ref": "#/definitions/Column"
},
{
"type": "null"
}
]
},
"success": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "null"
}
]
},
"error_message": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {
"MetadataValue": {
"anyOf": [
{
"type": "number"
},
{
"type": "string"
},
{
"type": "boolean"
}
]
},
"Metadata": {
"anyOf": [
{
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/MetadataValue"
}
},
{
"type": "object",
"additionalProperties": {
"type": [
"string",
"number",
"boolean",
"object",
"array",
"null"
]
}
}
]
},
"IndexedData": {
"type": "object",
"properties": {
"indices": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0
}
},
"values": {
"type": "array",
"items": {
"type": "number"
}
}
},
"required": [
"indices",
"values"
],
"additionalProperties": false
},
"Column": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"type": "number"
}
},
"metadata": {
"oneOf": [
{
"$ref": "#/definitions/Metadata"
},
{
"type": "null"
}
]
},
"indexed_data": {
"oneOf": [
{
"$ref": "#/definitions/IndexedData"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"values"
],
"additionalProperties": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"type": "object",
"properties": {
"code": {
"oneOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"message": {
"oneOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"details": {
"oneOf": [
{
"type": "array",
"items": {
"type": [
"string",
"number",
"boolean",
"object",
"array",
"null"
]
}
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {}
}
Loading
Loading