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

Created the CodeGenerationRequest #1716

Merged
merged 5 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ extension Target.Dependency {
static let grpc: Self = .target(name: grpcTargetName)
static let cgrpcZlib: Self = .target(name: cgrpcZlibTargetName)
static let protocGenGRPCSwift: Self = .target(name: "protoc-gen-grpc-swift")
static let grpcCodeGen: Self = .target(name: "GRPCCodeGen")

// Target dependencies; internal
static let grpcSampleData: Self = .target(name: "GRPCSampleData")
Expand Down Expand Up @@ -477,6 +478,11 @@ extension Target {
.copy("Generated")
]
)

static let grpcCodeGen: Target = .target(
name: "GRPCCodeGen",
path: "Sources/GRPCCodeGen"
)
}

// MARK: - Products
Expand Down
224 changes: 224 additions & 0 deletions Sources/GRPCCodeGen/CodeGenerationRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* Copyright 2023, gRPC Authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/// Describes the services, dependencies and trivia from an IDL file,
/// and the IDL itself through its specific serializer and deserializer.
public struct CodeGenerationRequest {
glbrntt marked this conversation as resolved.
Show resolved Hide resolved
/// The name of the source file containing the IDL, including the extension if applicable.
public var fileName: String

/// The Swift imports that the generated file depends on. The gRPC specific imports aren't required
/// as they will be added by default in the generated file.
///
/// - SeeAlso: ``Dependency``.
public var dependencies: [Dependency]

/// Any comments at the top of the file such as documentation and copyright headers.
/// They will be placed at the top of the generated file.
public var leadingTrivia: String

/// An array of service descriptors.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't tell me anything more than [ServiceDescriptor] tells me (i.e. what the thing is). If you say "A description of each service to generate." then it tells the user what it's used for.

///
/// - SeeAlso: ``ServiceDescriptor``.
public var services: [ServiceDescriptor]

/// Closure that receives the message type and returns a string representation of the
/// serializer call for that message type. The result is inserted in the string representing
/// the generated code, where clients or servers serialize their output.
///
/// For example: `lookupSerializer: {"ProtobufSerializer<\($0)>()"}`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Docs should start with a single sentence summary and add more detail in other paragraphs. Tools like Xcode and DocC show the first paragraph more prominently so it's quite important for it to be concise.

We can also be more specific about the requirements here: the serializer type must conform to MessageSerializer.

Suggested change
/// Closure that receives the message type and returns a string representation of the
/// serializer call for that message type. The result is inserted in the string representing
/// the generated code, where clients or servers serialize their output.
///
/// For example: `lookupSerializer: {"ProtobufSerializer<\($0)>()"}`.
/// Closure that receives a message type as a `String` and returns a code snippet to
/// initialize a `MessageSerializer` for that type as a `String`.
///
/// The result is inserted in the generated code, where clients serialize RPC inputs and
/// servers serialize RPC outputs.
///
/// For example, to serialize Protobuf messages you could specify a serializer as:
/// ```swift
/// request.lookupSerializer = { messageType in
/// "ProtobufSerializer<\(messageType)>()"
/// }
/// ```

public var lookupSerializer: (_ messageType: String) -> String

/// Closure that receives the message type and returns a string representation of the
/// deserializer call for that message type. The result is inserted in the string representing
/// the generated code, where clients or servers deserialize their input.
///
/// For example: `lookupDeserializer: {"ProtobufDeserializer<\($0)>()"}`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs a similar treatment to the one above, but the deserializer must conform to MessageDeserializer.

public var lookupDeserializer: (_ messageType: String) -> String

public init(
fileName: String,
dependencies: [Dependency] = [],
leadingTrivia: String,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Generally parameters should be specified in this order:

  1. required
  2. optional
  3. closures

In this case, I think we should probably just remove the defaults for dependencies and services. It's highly unlikely that there will be no dependencies or services.

We should also invert the order of dependencies and leadingTrivia, if you're reading a file top-to-bottom the leading trivial comes at the very top followed by the dependencies.

services: [ServiceDescriptor] = [],
lookupSerializer: @escaping (String) -> String,
lookupDeserializer: @escaping (String) -> String
) {
self.fileName = fileName
self.dependencies = dependencies
self.leadingTrivia = leadingTrivia
self.services = services
self.lookupSerializer = lookupSerializer
self.lookupDeserializer = lookupDeserializer
}

/// Represents an import: a module or a specific item from a module.
public struct Dependency {
/// If the dependency is an item, the property's value is the item representation.
/// If the dependency is a module, this property is nil.
public var item: Item? = nil

/// The name of the imported module or of the module an item is imported from.
public var module: String

public init(item: Item? = nil, module: String) {
self.item = item
self.module = module
}

/// Represents an item imported from a module.
public struct Item {
/// The keyword that specifies the item's kind (e.g. `func`, `struct`).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// The keyword that specifies the item's kind (e.g. `func`, `struct`).
/// The keyword that specifies the item's kind (e.g. `func`, `struct`).

public var kind: Kind

/// The imported item's symbol / name.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// The imported item's symbol / name.
/// The name of the imported item.

public var name: String

public init(kind: Kind, name: String) {
self.kind = kind
self.name = name
}
/// Represents the imported item's kind.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: leave a space between these

public struct Kind {
/// Describes the keyword associated with the imported item.
internal enum Value {
case `typealias`
case `struct`
case `class`
case `enum`
case `protocol`
case `let`
case `var`
case `func`
}

internal var value: Value

internal init(_ value: Value) {
self.value = value
}

/// The imported item is a typealias.
public static var `typealias`: Self {
Self(.`typealias`)
}

/// The imported item is a struct.
public static var `struct`: Self {
Self(.`struct`)
}

/// The imported item is a class.
public static var `class`: Self {
Self(.`class`)
}

/// The imported item is an enum.
public static var `enum`: Self {
Self(.`enum`)
}

/// The imported item is a protocol.
public static var `protocol`: Self {
Self(.`protocol`)
}

/// The imported item is a let.
public static var `let`: Self {
Self(.`let`)
}

/// The imported item is a var.
public static var `var`: Self {
Self(.`var`)
}

/// The imported item is a function.
public static var `func`: Self {
Self(.`func`)
}
}
}
}

/// Represents a service described in an IDL file.
public struct ServiceDescriptor {
/// Documentation from comments above the IDL service description.
public var documentation: String

/// Service name.
glbrntt marked this conversation as resolved.
Show resolved Hide resolved
public var name: String

/// The service namespace.
///
/// For `.proto` files it is the package name.
public var namespace: String

/// Array of descriptors for the methods of a service.
///
/// - SeeAlso: ``MethodDescriptor``.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment as on services, this doesn't tell me anything more than [MethodDescriptor].

public var methods: [MethodDescriptor]

public init(
documentation: String,
name: String,
namespace: String,
methods: [MethodDescriptor] = []
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we should default this, It'd be very strange to generate a service with no methods!

) {
self.documentation = documentation
self.name = name
self.namespace = namespace
self.methods = methods
}

/// Represents a method described in an IDL file.
public struct MethodDescriptor {
/// Documentation from comments above the IDL method description.
public var documentation: String

/// Method name.
public var name: String

/// Identifies if the method is input streaming.
public var isInputStreaming: Bool

/// Identifies if the method is output streaming.
public var isOutputStreaming: Bool

/// The generated input type for the described method.
public var inputType: String

/// The generated output type for the described method.
public var ouputType: String

public init(
documentation: String,
name: String,
isInputStreaming: Bool,
isOutputStreaming: Bool,
inputType: String,
ouputType: String
) {
self.documentation = documentation
self.name = name
self.isInputStreaming = isInputStreaming
self.isOutputStreaming = isOutputStreaming
self.inputType = inputType
self.ouputType = ouputType
}
}
}
}