blob: 90bde4a6c8d6d61ded41422cc742e949862e6a86 [file] [log] [blame] [edit]
// Copyright 2020-2025 Buf Technologies, Inc.
//
// 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.
package protosourcepath
import (
"slices"
"google.golang.org/protobuf/reflect/protoreflect"
)
const (
messageNameTypeTag = int32(1)
messageFieldsTypeTag = int32(2)
nestedMessagesTypeTag = int32(3)
nestedEnumsTypeTag = int32(4)
messageOneOfsTypeTag = int32(8)
messageOneOfNameTypeTag = int32(1)
messageOneOfOptionTypeTag = int32(2)
messageOptionTypeTag = int32(7)
messageExtensionsTypeTag = int32(6)
messageExtensionRangeTypeTag = int32(5)
messageExtensionRangeStartTypeTag = int32(1)
messageExtensionRangeEndTypeTag = int32(2)
messageExtensionRangeOptionTypeTag = int32(3)
messageReservedRangeTypeTag = int32(9)
messageReservedNameTypeTag = int32(10)
)
var (
terminalOneOfTokens = []int32{
messageOneOfNameTypeTag,
}
terminalExtensionRangeTokens = []int32{
messageExtensionRangeStartTypeTag,
messageExtensionRangeEndTypeTag,
}
)
// messages is the state when an element representing messages in the source path was parsed.
func messages(
_ int32,
fullSourcePath protoreflect.SourcePath,
index int,
excludeChildAssociatedPaths bool,
) (state, []protoreflect.SourcePath, error) {
associatedPaths := []protoreflect.SourcePath{
currentPath(fullSourcePath, index),
}
if !excludeChildAssociatedPaths {
associatedPaths = append(
associatedPaths,
childAssociatedPath(fullSourcePath, index, messageNameTypeTag),
)
}
if len(fullSourcePath) == index+1 {
// This does not extend beyond the message declaration, return associated paths and
// terminate here.
return nil, associatedPaths, nil
}
return message, associatedPaths, nil
}
// message is the state when an element representing a specific child path of a message was parsed.
func message(token int32, fullSourcePath protoreflect.SourcePath, index int, _ bool) (state, []protoreflect.SourcePath, error) {
switch token {
case messageNameTypeTag:
// The path for message name has already been added, can terminate here immediately.
return nil, nil, nil
case messageFieldsTypeTag:
// We check to make sure that the length of the source path contains at least the current
// token and an index. This is because all source paths for fields are expected
// to have indices.
if len(fullSourcePath) < index+2 {
return nil, nil, newInvalidSourcePathError(fullSourcePath, "cannot have field declaration without index")
}
return fields, nil, nil
case messageOneOfsTypeTag:
// We check to make sure that the length of the source path contains at least the current
// token and an index. This is because all source paths for oneofs are expected
// to have indices.
if len(fullSourcePath) < index+2 {
return nil, nil, newInvalidSourcePathError(fullSourcePath, "cannot have oneof declaration without index")
}
return oneOfs, nil, nil
case nestedMessagesTypeTag:
// We check to make sure that the length of the source path contains at least the current
// token and an index. This is because all source paths for nested messages are expected
// to have indices.
if len(fullSourcePath) < index+2 {
return nil, nil, newInvalidSourcePathError(fullSourcePath, "cannot have a nested message declaration without index")
}
return messages, nil, nil
case nestedEnumsTypeTag:
// We check to make sure that the length of the source path contains at least the current
// token and an index. This is because all source paths for nested enums are expected
// to have indices.
if len(fullSourcePath) < index+2 {
return nil, nil, newInvalidSourcePathError(fullSourcePath, "cannot have a nested enum declaration without index")
}
return enums, nil, nil
case messageOptionTypeTag:
// For options, we add the full path and then return the options state to validate
// the path.
return options, []protoreflect.SourcePath{slices.Clone(fullSourcePath)}, nil
case messageExtensionRangeTypeTag:
// For extension ranges, we add the full path and then return the extension ranges state
// to validate the path.
return extensionRanges, []protoreflect.SourcePath{currentPath(fullSourcePath, index)}, nil
case messageExtensionsTypeTag:
// For extensions, we add the full path and then return the extensions state to
// validate the path.
return extensions, []protoreflect.SourcePath{currentPath(fullSourcePath, index)}, nil
case messageReservedRangeTypeTag:
// For reserved ranges, we add the full path and then return the reserved ranges state
// to validate the path.
return reservedRanges, []protoreflect.SourcePath{currentPath(fullSourcePath, index)}, nil
case messageReservedNameTypeTag:
// For reserved names, we add the full path and then return the reserved names state to
// validate the path.
return reservedNames, []protoreflect.SourcePath{currentPath(fullSourcePath, index)}, nil
}
return nil, nil, newInvalidSourcePathError(fullSourcePath, "invalid message path")
}
// oneOfs is the state when an element representing oneofs in the source path was parsed.
func oneOfs(
_ int32,
fullSourcePath protoreflect.SourcePath,
index int,
excludeChildAssociatedPaths bool,
) (state, []protoreflect.SourcePath, error) {
associatedPaths := []protoreflect.SourcePath{
currentPath(fullSourcePath, index),
}
if !excludeChildAssociatedPaths {
associatedPaths = append(
associatedPaths,
childAssociatedPath(fullSourcePath, index, messageOneOfNameTypeTag),
)
}
return oneOf, associatedPaths, nil
}
// oneOf is the state when an element representing a specific child path of a oneof was parsed.
func oneOf(token int32, fullSourcePath protoreflect.SourcePath, _ int, _ bool) (state, []protoreflect.SourcePath, error) {
if slices.Contains(terminalOneOfTokens, token) {
// Encountered a terminal one of token, can terminate here immediately.
return nil, nil, nil
}
switch token {
case messageOneOfOptionTypeTag:
// For options, we add the full path and then return the options state to validate
// the path.
return options, []protoreflect.SourcePath{slices.Clone(fullSourcePath)}, nil
}
return nil, nil, newInvalidSourcePathError(fullSourcePath, "invalid one of path")
}
// extensionRanges is the state when an element representing extension ranges in the source path was parsed.
func extensionRanges(
_ int32,
fullSourcePath protoreflect.SourcePath,
index int,
excludeChildAssociatedPaths bool,
) (state, []protoreflect.SourcePath, error) {
associatedPaths := []protoreflect.SourcePath{
currentPath(fullSourcePath, index),
}
if !excludeChildAssociatedPaths {
associatedPaths = append(
associatedPaths,
childAssociatedPath(fullSourcePath, index, messageExtensionRangeStartTypeTag),
childAssociatedPath(fullSourcePath, index, messageExtensionRangeEndTypeTag),
)
}
if len(fullSourcePath) == index+1 {
// This does not extend beyond the declaration, return associated paths and terminate here.
return nil, associatedPaths, nil
}
return extensionRange, associatedPaths, nil
}
// extensionRange is the state when an element representing a specific child path of an
// extension range was parsed.
func extensionRange(token int32, fullSourcePath protoreflect.SourcePath, _ int, _ bool) (state, []protoreflect.SourcePath, error) {
if slices.Contains(terminalExtensionRangeTokens, token) {
// Encountered a terminal extension range token, can terminate here immediately.
return nil, nil, nil
}
switch token {
case messageExtensionRangeOptionTypeTag:
// For options, we add the full path and then return the options state to validate
// the path.
return options, []protoreflect.SourcePath{slices.Clone(fullSourcePath)}, nil
}
return nil, nil, newInvalidSourcePathError(fullSourcePath, "invalid extension range path")
}