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

Support feature resolution for Protobuf Editions. #473

Merged
merged 4 commits into from
Oct 1, 2024
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
45 changes: 45 additions & 0 deletions proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Defaults.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
-- 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.Defaults
Description: Exports defaults for native features in Protobuf Editions.
Copyright: Copyright (c) 2024 Google LLC
License: BSD3
-}
module Data.ProtoLens.Compiler.Editions.Defaults (nativeDefaults) where

import Data.ByteString (ByteString)
import Data.ProtoLens (decodeMessage)
import Proto.Google.Protobuf.Descriptor (FeatureSetDefaults)

{-| 'FeatureSetDefaults' message containing feature defaults.

This contains the defaults from editions @LEGACY@ to @EDITION_2023@
for the native features defined by @google.protobuf.FeatureSet@.
-}
nativeDefaults :: FeatureSetDefaults
nativeDefaults
| Right m <- msg = m
| Left e <- msg = error $ "unable to decode built-in defaults: " ++ e
where msg = decodeMessage serializedNativeDefaults
chungyc marked this conversation as resolved.
Show resolved Hide resolved

{-| Serialized 'FeatureSetDefaults' message containing feature defaults.

This contains the defaults from editions @LEGACY@ to @EDITION_2023@
for the native features defined by @google.protobuf.FeatureSet@.
The message was generated with @protoc@ and translated into a Haskell string:

> $ protoc --edition_defaults_out=defaults.binpb --edition_defaults_minimum=LEGACY --edition_defaults_maximum=2023 google/protobuf/descriptor.proto
> $ ghci
> ghci> import Data.ByteString as B
> ghci> B.readFile "defaults.binpb" >>= print . show

-}
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\""
86 changes: 86 additions & 0 deletions proto-lens-protoc/app/Data/ProtoLens/Compiler/Editions/Features.hs
Original file line number Diff line number Diff line change
@@ -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,
chungyc marked this conversation as resolved.
Show resolved Hide resolved
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)
4 changes: 3 additions & 1 deletion proto-lens-protoc/proto-lens-protoc.cabal
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.36.0.
-- This file has been generated from package.yaml by hpack version 0.37.0.
--
-- see: https://github.com/sol/hpack

Expand Down Expand Up @@ -42,6 +42,8 @@ executable proto-lens-protoc
main-is: protoc-gen-haskell.hs
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
Expand Down
Loading