From 029816802f43a9de48ed39a5f904951dead2bbd8 Mon Sep 17 00:00:00 2001 From: Yoo Chung Date: Wed, 11 Sep 2024 00:33:19 +0000 Subject: [PATCH] Implement feature resolution for Protobuf Editions. --- .../ProtoLens/Compiler/Editions/Defaults.hs | 15 ++-- .../ProtoLens/Compiler/Editions/Features.hs | 86 +++++++++++++++++++ proto-lens-protoc/proto-lens-protoc.cabal | 1 + 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Features.hs diff --git a/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Defaults.hs b/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Defaults.hs index cd3e4345..d464f81b 100644 --- a/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Defaults.hs +++ b/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Defaults.hs @@ -12,7 +12,7 @@ Description: Exports defaults for native features in Protobuf Editions. Copyright: Copyright (c) 2024 Google LLC License: BSD3 -} -module Data.ProtoLens.Compiler.Editions.Defaults (defaults) where +module Data.ProtoLens.Compiler.Editions.Defaults (nativeDefaults) where import Data.ByteString (ByteString) import Data.ProtoLens (decodeMessage) @@ -23,10 +23,11 @@ import Proto.Google.Protobuf.Descriptor (FeatureSetDefaults) This contains the defaults from editions @LEGACY@ to @EDITION_2023@ for the native features defined by @google.protobuf.FeatureSet@. -} -defaults :: FeatureSetDefaults -defaults | Right m <- msg = m - | Left e <- msg = error $ "unable to decode built-in defaults: " ++ e - where msg = decodeMessage serializedDefaults +nativeDefaults :: FeatureSetDefaults +nativeDefaults + | Right m <- msg = m + | Left e <- msg = error $ "unable to decode built-in defaults: " ++ e + where msg = decodeMessage serializedNativeDefaults {-| Serialized 'FeatureSetDefaults' message containing feature defaults. @@ -40,5 +41,5 @@ The message was generated with @protoc@ and translated into a Haskell string: > ghci> B.readFile "defaults.binpb" >>= print . show -} -serializedDefaults :: ByteString -serializedDefaults = read "\"\\n\\DC3\\CAN\\132\\a\\\"\\NUL*\\f\\b\\SOH\\DLE\\STX\\CAN\\STX \\ETX(\\SOH0\\STX\\n\\DC3\\CAN\\231\\a\\\"\\NUL*\\f\\b\\STX\\DLE\\SOH\\CAN\\SOH \\STX(\\SOH0\\SOH\\n\\DC3\\CAN\\232\\a\\\"\\f\\b\\SOH\\DLE\\SOH\\CAN\\SOH \\STX(\\SOH0\\SOH*\\NUL \\132\\a(\\232\\a\"" +serializedNativeDefaults :: ByteString +serializedNativeDefaults = read "\"\\n\\DC3\\CAN\\132\\a\\\"\\NUL*\\f\\b\\SOH\\DLE\\STX\\CAN\\STX \\ETX(\\SOH0\\STX\\n\\DC3\\CAN\\231\\a\\\"\\NUL*\\f\\b\\STX\\DLE\\SOH\\CAN\\SOH \\STX(\\SOH0\\SOH\\n\\DC3\\CAN\\232\\a\\\"\\f\\b\\SOH\\DLE\\SOH\\CAN\\SOH \\STX(\\SOH0\\SOH*\\NUL \\132\\a(\\232\\a\"" diff --git a/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Features.hs b/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Features.hs new file mode 100644 index 00000000..9f523a88 --- /dev/null +++ b/proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Features.hs @@ -0,0 +1,86 @@ +-- Copyright 2024 Google LLC. All Rights Reserved. +-- +-- Use of this source code is governed by a BSD-style +-- license that can be found in the LICENSE file or at +-- https://developers.google.com/open-source/licenses/bsd + +{-# LANGUAGE OverloadedLabels #-} + +{-| +Module: Data.ProtoLens.Compiler.Editions.Features +Description: Resolves a feature set for a particular edition with Protobuf Editions. +Copyright: Copyright (c) 2024 Google LLC +License: BSD3 +-} +module Data.ProtoLens.Compiler.Editions.Features + ( featuresForEdition + , featuresForEditionFromDefaults + , mergedInto + ) where + +import Control.Applicative ((<|>)) +import Data.ProtoLens (defMessage) +import Data.ProtoLens.Compiler.Editions.Defaults (nativeDefaults) +import Data.ProtoLens.Labels () +import Lens.Family2 ((^.), (.~), (&)) +import Proto.Google.Protobuf.Descriptor + ( Edition + , FeatureSet + , FeatureSetDefaults) + +{-| +Returns the native feature set defaults for the given edition. + +Native features refer to the fields directly defined by 'FeatureSet'. +Features defined as extensions of 'FeatureSet' would be custom features. +-} +featuresForEdition :: Edition -> FeatureSet +featuresForEdition = featuresForEditionFromDefaults nativeDefaults + +{-| +Given the feature set defaults for multiple editions, +return the feature set defaults for the given edition. + +If extensions were supported, this could be used directly +to resolve custom features defined as extensions of 'FeatureSet' +for a particular edition. +-} +featuresForEditionFromDefaults :: FeatureSetDefaults -> Edition -> FeatureSet +featuresForEditionFromDefaults defaults edition + | (d : _) <- candidates = (d ^. #overridableFeatures) `mergedInto` (d ^. #fixedFeatures) + | otherwise = defMessage + where + candidates = dropWhile (\d -> d ^. #edition > edition) recentFirst + + -- #defaults is supposed to be in ascending order of editions + recentFirst = reverse $ defaults ^. #defaults + +{-| +Returns the result of merging a 'FeatureSet' message into another 'FeatureSet' message. + +The semantics are the same as @MergeFrom@ in C++. +When merging a message A into a message B, then any field that has a value in A +will override the value of the same field in B, +otherwise the field in B is used as is. + +Consider using this function as an infix operator. +For example, + +>>> let c = a `mergedInto` b + +could be read like "message C is the message A merged into message B". + +If merging was generally supported for all messages, +we would use it directly instead of using this custom implementation for 'FeatureSet'. +This does not support merging extensions or unknown fields. +-} +mergedInto :: FeatureSet -- ^ Feature set to merge from. + -> FeatureSet -- ^ Feature set to merge into. + -> FeatureSet -- ^ The merged feature set. +mergedInto a b = defMessage + & #maybe'fieldPresence .~ (a ^. #maybe'fieldPresence <|> b ^. #maybe'fieldPresence) + & #maybe'enumType .~ (a ^. #maybe'enumType <|> b ^. #maybe'enumType) + & #maybe'repeatedFieldEncoding .~ (a ^. #maybe'repeatedFieldEncoding <|> b ^. #maybe'repeatedFieldEncoding) + & #maybe'utf8Validation .~ (a ^. #maybe'utf8Validation <|> b ^. #maybe'utf8Validation) + & #maybe'messageEncoding .~ (a ^. #maybe'messageEncoding <|> b ^. #maybe'messageEncoding) + & #maybe'jsonFormat .~ (a ^. #maybe'jsonFormat <|> b ^. #maybe'jsonFormat) diff --git a/proto-lens-protoc/proto-lens-protoc.cabal b/proto-lens-protoc/proto-lens-protoc.cabal index 29157018..e30c5f63 100644 --- a/proto-lens-protoc/proto-lens-protoc.cabal +++ b/proto-lens-protoc/proto-lens-protoc.cabal @@ -43,6 +43,7 @@ executable proto-lens-protoc other-modules: Data.ProtoLens.Compiler.Definitions Data.ProtoLens.Compiler.Editions.Defaults + Data.ProtoLens.Compiler.Editions.Features Data.ProtoLens.Compiler.Generate Data.ProtoLens.Compiler.Generate.Commented Data.ProtoLens.Compiler.Generate.Encoding