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
4 changes: 4 additions & 0 deletions lib/proto_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ module ProtoPlugin

require_relative "proto_plugin/utils"
require_relative "proto_plugin/context"
require_relative "proto_plugin/commentable"
require_relative "proto_plugin/file_descriptor"
require_relative "proto_plugin/enum_descriptor"
require_relative "proto_plugin/enum_value_descriptor"
require_relative "proto_plugin/field_descriptor"
require_relative "proto_plugin/oneof_descriptor"
require_relative "proto_plugin/message_descriptor"
require_relative "proto_plugin/service_descriptor"
require_relative "proto_plugin/method_descriptor"
Expand Down
62 changes: 62 additions & 0 deletions lib/proto_plugin/commentable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module ProtoPlugin
# A mixin providing access to the comments associated with a descriptor via
# its file's `SourceCodeInfo`.
#
# Including classes must respond to `#file` (returning the {FileDescriptor}
# the element belongs to) and `#descriptor` (returning the raw descriptor
# proto the comments are keyed against).
#
# @see https://github.com/protocolbuffers/protobuf/blob/v28.2/src/google/protobuf/descriptor.proto#L1213
# Google::Protobuf::SourceCodeInfo::Location
module Commentable
# The `SourceCodeInfo::Location` associated with this element, if source
# info was included in the request.
#
# @return [Google::Protobuf::SourceCodeInfo::Location]
# @return [nil] if no location is available
def source_location
file&.location_for(descriptor)
end

# The comment block appearing directly above this element.
#
# @return [String] the leading comment, as provided by `protoc`
# @return [nil] if there is no leading comment
def leading_comments
presence(source_location&.leading_comments)
end

# The comment appearing directly after this element on the same or
# following line.
#
# @return [String] the trailing comment, as provided by `protoc`
# @return [nil] if there is no trailing comment
def trailing_comments
presence(source_location&.trailing_comments)
end

# The comments attached to this element, in source order: the leading
# comment followed by the trailing comment. Absent blocks are omitted.
#
# Detached comments (blocks the author separated from the element with a
# blank line) are not exposed. Per protoc they appear before "but [are]
# not connected to" the element, so they are file organization rather than
# documentation of any element.
#
# @example
# field.comments.join("\n").strip
#
# @return [Array<String>]
def comments
[leading_comments, trailing_comments].compact
end

private

def presence(value)
value unless value.nil? || value.empty?
end
end
end
21 changes: 21 additions & 0 deletions lib/proto_plugin/enum_descriptor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ module ProtoPlugin
#
# @see https://github.com/protocolbuffers/protobuf/blob/v28.2/src/google/protobuf/descriptor.proto#L336
class EnumDescriptor < SimpleDelegator
include Commentable

# @return [Google::Protobuf::EnumDescriptorProto]
attr_reader :descriptor

Expand All @@ -28,6 +30,25 @@ def initialize(descriptor, parent)
@parent = parent
end

# The file descriptor this enum belongs to.
#
# @return [FileDescriptor]
def file
parent.file
end

# The values defined for this enum.
#
# @return [Array<EnumValueDescriptor>]
#
# @see https://github.com/protocolbuffers/protobuf/blob/v28.2/src/google/protobuf/descriptor.proto#L343
# Google::Protobuf::EnumDescriptorProto#value
def values
@values ||= @descriptor.value.map do |v|
EnumValueDescriptor.new(v, self)
end
end

# The full name of the enum, including parent namespace.
#
# @example
Expand Down
49 changes: 49 additions & 0 deletions lib/proto_plugin/enum_value_descriptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require "delegate"

module ProtoPlugin
# A wrapper class around `Google::Protobuf::EnumValueDescriptorProto`
# which provides helpers and more idiomatic Ruby access patterns.
#
# Any method not defined directly is delegated to the descriptor the wrapper was initialized with.
#
# @see https://github.com/protocolbuffers/protobuf/blob/v28.2/src/google/protobuf/descriptor.proto#L356
# Google::Protobuf::EnumValueDescriptorProto
class EnumValueDescriptor < SimpleDelegator
include Commentable

# @return [Google::Protobuf::EnumValueDescriptorProto]
attr_reader :descriptor

# The enum descriptor this value was defined within.
#
# @return [EnumDescriptor]
attr_reader :enum

# @param descriptor [Google::Protobuf::EnumValueDescriptorProto]
# @param enum [EnumDescriptor] The enum this value was defined within.
def initialize(descriptor, enum)
super(descriptor)
@descriptor = descriptor
@enum = enum
end

# The file descriptor this enum value belongs to.
#
# @return [FileDescriptor]
def file
enum.file
end

# The full name of the enum value, including parent namespace.
#
# @example
# "My::Ruby::Package::EnumName::VALUE_NAME"
#
# @return [String]
def full_name
@full_name ||= "#{enum.full_name}::#{name}"
end
end
end
136 changes: 136 additions & 0 deletions lib/proto_plugin/field_descriptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require "delegate"

module ProtoPlugin
# A wrapper class around `Google::Protobuf::FieldDescriptorProto`
# which provides helpers and more idiomatic Ruby access patterns.
#
# Any method not defined directly is delegated to the descriptor the wrapper was initialized with.
#
# @see https://github.com/protocolbuffers/protobuf/blob/v28.2/src/google/protobuf/descriptor.proto#L242
# Google::Protobuf::FieldDescriptorProto
class FieldDescriptor < SimpleDelegator
include Commentable

# @return [Google::Protobuf::FieldDescriptorProto]
attr_reader :descriptor

# The message descriptor this field was defined within.
#
# @return [MessageDescriptor]
attr_reader :message

# @param descriptor [Google::Protobuf::FieldDescriptorProto]
# @param message [MessageDescriptor] The message this field was defined within.
# @param context [Context]
def initialize(descriptor, message, context)
super(descriptor)
@descriptor = descriptor
@message = message
@context = context
end

# The file descriptor this field belongs to.
#
# @return [FileDescriptor]
def file
message.file
end

# Resolves the message or enum descriptor referenced by this field.
#
# Only message, enum, and group fields reference another type. For scalar
# fields (or when the referenced type was not included in the request),
# `nil` is returned.
#
# @return [MessageDescriptor] if the field is a message or group type
# @return [EnumDescriptor] if the field is an enum type
# @return [nil] if the field is a scalar type or the type was not found
def type_descriptor
return if scalar?

@context.type_by_proto_name(type_name)
end

# Returns true if the field is a message type.
#
# @return [Boolean]
def message?
type == :TYPE_MESSAGE
end

# Returns true if the field is an enum type.
#
# @return [Boolean]
def enum?
type == :TYPE_ENUM
end

# Returns true if the field is a group type.
#
# @return [Boolean]
def group?
type == :TYPE_GROUP
end

# Returns true if the field is a scalar type (i.e. not a message, enum, or group).
#
# @return [Boolean]
def scalar?
!message? && !enum? && !group?
end

# Returns true if the field has the `repeated` label.
#
# @return [Boolean]
def repeated?
label == :LABEL_REPEATED
end

# Returns true if the field has the `required` label (proto2 only).
#
# @return [Boolean]
def required?
label == :LABEL_REQUIRED
end

# Returns true if the field has the `optional` label.
#
# @note In proto3 all singular fields carry the `optional` label internally.
# Use {#proto3_optional?} to detect fields with explicit presence tracking.
#
# @return [Boolean]
def optional?
label == :LABEL_OPTIONAL
end

# Returns true if the field was declared with proto3 explicit presence,
# i.e. an `optional` keyword in a proto3 file.
#
# @return [Boolean]
def proto3_optional?
descriptor.proto3_optional
end

# Returns true if the field is a member of a oneof.
#
# @note Fields declared with the proto3 `optional` keyword are backed by a
# synthetic oneof. Those are not considered oneof members here.
#
# @return [Boolean]
def oneof?
descriptor.has_oneof_index? && !proto3_optional?
end

# The oneof this field is a member of, if any.
#
# @return [OneofDescriptor] if the field is a member of a oneof
# @return [nil] otherwise
def oneof
return unless oneof?

message.oneofs[descriptor.oneof_index]
end
end
end
Loading
Loading