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

Simplify code gen lib interface #2169

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
210 changes: 171 additions & 39 deletions Sources/GRPCCodeGen/CodeGenerationRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public struct CodeGenerationRequest {
///
/// For example, to serialize Protobuf messages you could specify a serializer as:
/// ```swift
/// request.lookupSerializer = { messageType in
/// request.makeSerializerCodeSnippet = { messageType in
/// "ProtobufSerializer<\(messageType)>()"
/// }
/// ```
public var lookupSerializer: (_ messageType: String) -> String
public var makeSerializerCodeSnippet: (_ messageType: String) -> String

/// Closure that receives a message type as a `String` and returns a code snippet to
/// initialize a `MessageDeserializer` for that type as a `String`.
Expand All @@ -58,26 +58,64 @@ public struct CodeGenerationRequest {
///
/// For example, to serialize Protobuf messages you could specify a serializer as:
/// ```swift
/// request.lookupDeserializer = { messageType in
/// request.makeDeserializerCodeSnippet = { messageType in
/// "ProtobufDeserializer<\(messageType)>()"
/// }
/// ```
public var lookupDeserializer: (_ messageType: String) -> String
public var makeDeserializerCodeSnippet: (_ messageType: String) -> String

public init(
fileName: String,
leadingTrivia: String,
dependencies: [Dependency],
services: [ServiceDescriptor],
lookupSerializer: @escaping (String) -> String,
lookupDeserializer: @escaping (String) -> String
makeSerializerCodeSnippet: @escaping (_ messageType: String) -> String,
makeDeserializerCodeSnippet: @escaping (_ messageType: String) -> String
) {
self.fileName = fileName
self.leadingTrivia = leadingTrivia
self.dependencies = dependencies
self.services = services
self.lookupSerializer = lookupSerializer
self.lookupDeserializer = lookupDeserializer
self.makeSerializerCodeSnippet = makeSerializerCodeSnippet
self.makeDeserializerCodeSnippet = makeDeserializerCodeSnippet
}
}

extension CodeGenerationRequest {
@available(*, deprecated, renamed: "makeSerializerSnippet")
public var lookupSerializer: (_ messageType: String) -> String {
get { self.makeSerializerCodeSnippet }
set { self.makeSerializerCodeSnippet = newValue }
}

@available(*, deprecated, renamed: "makeDeserializerSnippet")
public var lookupDeserializer: (_ messageType: String) -> String {
get { self.makeDeserializerCodeSnippet }
set { self.makeDeserializerCodeSnippet = newValue }
}

@available(
*,
deprecated,
renamed:
"init(fileName:leadingTrivia:dependencies:services:lookupSerializer:lookupDeserializer:)"
)
public init(
fileName: String,
leadingTrivia: String,
dependencies: [Dependency],
services: [ServiceDescriptor],
lookupSerializer: @escaping (String) -> String,
lookupDeserializer: @escaping (String) -> String
) {
self.init(
fileName: fileName,
leadingTrivia: leadingTrivia,
dependencies: dependencies,
services: services,
makeSerializerCodeSnippet: lookupSerializer,
makeDeserializerCodeSnippet: lookupDeserializer
)
}
}

Expand All @@ -88,7 +126,7 @@ public struct Dependency: Equatable {
public var item: Item?

/// The access level to be included in imports of this dependency.
public var accessLevel: SourceGenerator.Config.AccessLevel
public var accessLevel: CodeGenerator.Config.AccessLevel

/// The name of the imported module or of the module an item is imported from.
public var module: String
Expand All @@ -107,7 +145,7 @@ public struct Dependency: Equatable {
module: String,
spi: String? = nil,
preconcurrency: PreconcurrencyRequirement = .notRequired,
accessLevel: SourceGenerator.Config.AccessLevel
accessLevel: CodeGenerator.Config.AccessLevel
) {
self.item = item
self.module = module
Expand Down Expand Up @@ -228,33 +266,53 @@ public struct ServiceDescriptor: Hashable {
/// It is already formatted, meaning it contains "///" and new lines.
public var documentation: String

/// The service name in different formats.
///
/// All properties of this object must be unique for each service from within a namespace.
public var name: Name

/// The service namespace in different formats.
///
/// All different services from within the same namespace must have
/// the same ``Name`` object as this property.
/// For `.proto` files the base name of this object is the package name.
public var namespace: Name
/// The name of the service.
public var name: ServiceName

/// A description of each method of a service.
///
/// - SeeAlso: ``MethodDescriptor``.
public var methods: [MethodDescriptor]

public init(
documentation: String,
name: ServiceName,
methods: [MethodDescriptor]
) {
self.documentation = documentation
self.name = name
self.methods = methods
}
}

extension ServiceDescriptor {
@available(*, deprecated, renamed: "init(documentation:name:methods:)")
public init(
documentation: String,
name: Name,
namespace: Name,
methods: [MethodDescriptor]
) {
self.documentation = documentation
self.name = name
self.namespace = namespace
self.methods = methods

let identifier = namespace.base.isEmpty ? name.base : namespace.base + "." + name.base

let upper =
namespace.generatedUpperCase.isEmpty
? name.generatedUpperCase
: namespace.generatedUpperCase + "_" + name.generatedUpperCase

let lower =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, but can we rename these variable names to identifyingName, typeName and propertyName?
Before looking at the tests, I was very confused about the names and what each was supposed to be 😅 (e.g. it's called lower but we use name.generatedUpperCase, etc).

Copy link
Collaborator Author

@glbrntt glbrntt Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, of course. I'm not sure why I used upper and lower here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, WDYT of identifyingName? I'm not 100% happy with it but wasn't sure about alternatives.

namespace.generatedLowerCase.isEmpty
? name.generatedUpperCase
: namespace.generatedLowerCase + "_" + name.generatedUpperCase

self.name = ServiceName(
identifyingName: identifier,
typeName: upper,
propertyName: lower
)
}
}

Expand All @@ -268,7 +326,7 @@ public struct MethodDescriptor: Hashable {
///
/// All properties of this object must be unique for each method
/// from within a service.
public var name: Name
public var name: MethodName

/// Identifies if the method is input streaming.
public var isInputStreaming: Bool
Expand All @@ -284,7 +342,7 @@ public struct MethodDescriptor: Hashable {

public init(
documentation: String,
name: Name,
name: MethodName,
isInputStreaming: Bool,
isOutputStreaming: Bool,
inputType: String,
Expand All @@ -299,7 +357,94 @@ public struct MethodDescriptor: Hashable {
}
}

extension MethodDescriptor {
@available(*, deprecated, message: "Use MethodName instead of Name")
public init(
documentation: String,
name: Name,
isInputStreaming: Bool,
isOutputStreaming: Bool,
inputType: String,
outputType: String
) {
self.documentation = documentation
self.name = MethodName(
identifyingName: name.base,
typeName: name.generatedUpperCase,
functionName: name.generatedLowerCase
)
self.isInputStreaming = isInputStreaming
self.isOutputStreaming = isOutputStreaming
self.inputType = inputType
self.outputType = outputType
}
}

public struct ServiceName: Hashable {
/// The identifying name as used in the service/method descriptors including any namespace.
///
/// This value is also used to identify the service to the remote peer, usually as part of the
/// ":path" pseudoheader if doing gRPC over HTTP/2.
///
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
/// value should be "foo.bar.Baz".
public var identifyingName: String

/// The name as used on types including any namespace.
///
/// This is used to generate a namespace for each service which contains a number of client and
/// server protocols and concrete types.
///
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
/// value should be "Foo\_Bar\_Baz".
public var typeName: String

/// The name as used as a property.
///
/// This is used to provide a convenience getter for a descriptor of the service.
///
/// If the service is declared in package "foo.bar" and the service is called "Baz" then this
/// value should be "foo\_bar\_Baz".
public var propertyName: String

public init(identifyingName: String, typeName: String, propertyName: String) {
self.identifyingName = identifyingName
self.typeName = typeName
self.propertyName = propertyName
}
}

public struct MethodName: Hashable {
/// The identifying name as used in the service/method descriptors.
///
/// This value is also used to identify the method to the remote peer, usually as part of the
/// ":path" pseudoheader if doing gRPC over HTTP/2.
///
/// This value typically starts with an uppercase character, for example "Get".
public var identifyingName: String

/// The name as used on types including any namespace.
///
/// This is used to generate a namespace for each method which contains information about
/// the method.
///
/// This value typically starts with an uppercase character, for example "Get".
public var typeName: String

/// The name as used as a property.
///
/// This value typically starts with an lowercase character, for example "get".
public var functionName: String

public init(identifyingName: String, typeName: String, functionName: String) {
self.identifyingName = identifyingName
self.typeName = typeName
self.functionName = functionName
}
}

/// Represents the name associated with a namespace, service or a method, in three different formats.
@available(*, deprecated, message: "Use ServiceName/MethodName instead.")
public struct Name: Hashable {
/// The base name is the name used for the namespace/service/method in the IDL file, so it should follow
/// the specific casing of the IDL.
Expand Down Expand Up @@ -327,6 +472,7 @@ public struct Name: Hashable {
}
}

@available(*, deprecated, message: "Use ServiceName/MethodName instead.")
extension Name {
/// The base name replacing occurrences of "." with "_".
///
Expand All @@ -335,17 +481,3 @@ extension Name {
return self.base.replacing(".", with: "_")
}
}

extension ServiceDescriptor {
var namespacedServicePropertyName: String {
let prefix: String

if self.namespace.normalizedBase.isEmpty {
prefix = ""
} else {
prefix = self.namespace.normalizedBase + "_"
}

return prefix + self.name.normalizedBase
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
* limitations under the License.
*/

/// Creates a ``SourceFile`` containing the generated code for the RPCs represented in a ``CodeGenerationRequest`` object.
public struct SourceGenerator: Sendable {
@available(*, deprecated, renamed: "CodeGenerator")
public typealias SourceGenerator = CodeGenerator

/// Generates ``SourceFile`` objects containing generated code for the RPCs represented
/// in a ``CodeGenerationRequest`` object.
public struct CodeGenerator: Sendable {
/// The options regarding the access level, indentation for the generated code
/// and whether to generate server and client code.
public var config: Config
Expand Down Expand Up @@ -79,8 +83,8 @@ public struct SourceGenerator: Sendable {
}
}

/// The function that transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
/// the generated code, in accordance to the configurations set by the user for the ``SourceGenerator``.
/// Transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing
/// the generated code.
public func generate(
_ request: CodeGenerationRequest
) throws -> SourceFile {
Expand Down
14 changes: 7 additions & 7 deletions Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ extension ProtocolDescription {
.preFormatted(docs(for: method)),
.function(
signature: .clientMethod(
name: method.name.generatedLowerCase,
name: method.name.functionName,
input: method.inputType,
output: method.outputType,
streamingInput: method.isInputStreaming,
Expand Down Expand Up @@ -256,7 +256,7 @@ extension ExtensionDescription {
.function(
.clientMethodWithDefaults(
accessLevel: accessLevel,
name: method.name.generatedLowerCase,
name: method.name.functionName,
input: method.inputType,
output: method.outputType,
streamingInput: method.isInputStreaming,
Expand Down Expand Up @@ -506,7 +506,7 @@ extension ExtensionDescription {
.function(
.clientMethodExploded(
accessLevel: accessLevel,
name: method.name.generatedLowerCase,
name: method.name.functionName,
input: method.inputType,
output: method.outputType,
streamingInput: method.isInputStreaming,
Expand Down Expand Up @@ -716,11 +716,11 @@ extension StructDescription {
.function(
.clientMethod(
accessLevel: accessLevel,
name: method.name.generatedLowerCase,
name: method.name.functionName,
input: method.inputType,
output: method.outputType,
serviceEnum: serviceEnum,
methodEnum: method.name.generatedUpperCase,
methodEnum: method.name.typeName,
streamingInput: method.isInputStreaming,
streamingOutput: method.isOutputStreaming
)
Expand All @@ -735,7 +735,7 @@ private func docs(
for method: MethodDescriptor,
serializers includeSerializers: Bool = true
) -> String {
let summary = "/// Call the \"\(method.name.base)\" method."
let summary = "/// Call the \"\(method.name.identifyingName)\" method."

let request: String
if method.isInputStreaming {
Expand Down Expand Up @@ -773,7 +773,7 @@ private func docs(
}

private func explodedDocs(for method: MethodDescriptor) -> String {
let summary = "/// Call the \"\(method.name.base)\" method."
let summary = "/// Call the \"\(method.name.identifyingName)\" method."
var parameters = """
/// - Parameters:
"""
Expand Down
Loading
Loading