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

Add deprecated fields support #89

Merged
merged 1 commit into from
Oct 11, 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
5 changes: 5 additions & 0 deletions .golden/kotlinDeprecatedFieldSpec/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
data class Data(
val field0: Int,
// Deprecated since build 500
// val field1: Int? = null,
)
5 changes: 5 additions & 0 deletions .golden/swiftDeprecatedFieldSpec/golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
struct Data {
var field0: Int
// Deprecated since build 500
// var field1: Int?
}
3 changes: 2 additions & 1 deletion moat.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.35.2.
-- This file has been generated from package.yaml by hpack version 0.37.0.
--
-- see: https://github.com/sol/hpack

Expand Down Expand Up @@ -80,6 +80,7 @@ test-suite spec
BasicNewtypeWithEitherFieldSpec
BasicRecordSpec
Common
DeprecatedFieldSpec
DuplicateRecordFieldSpec
EnumValueClassDocSpec
EnumValueClassSpec
Expand Down
45 changes: 35 additions & 10 deletions src/Moat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ module Moat
omitFields,
omitCases,
fieldsRequiredByClients,
deprecatedFields,
strictCases,
makeBase,
sumOfProductEncodingOptions,
Expand Down Expand Up @@ -509,6 +510,10 @@ data MoatError
}
| MissingRequiredFields
{ _missingFields :: [String]
, _missingDeprecatedFields :: [String]
}
| MissingDeprecatedRequiredFields
{ _missingDeprecatedRequiredFields :: [String]
}
| MissingStrictCases
{ _missingCases :: [String]
Expand Down Expand Up @@ -577,8 +582,10 @@ prettyMoatError = \case
ImproperNewtypeConstructorInfo conInfo ->
"Expected `ConstructorInfo` with single field, but got "
++ show conInfo
MissingRequiredFields missingFields ->
"These fields are required by clients: " ++ L.unwords missingFields
MissingRequiredFields missingFields missingDeprecatedFields ->
"These fields are required by clients: " ++ L.unwords missingFields ++ " " ++ L.unwords missingDeprecatedFields
MissingDeprecatedRequiredFields missingDeprecatedFields ->
"These fields need to be added to the required field list due to being necessary on older clients: " ++ L.unwords missingDeprecatedFields
MissingStrictCases missingCases ->
"Removing these cases will break clients: " ++ L.unwords missingCases

Expand Down Expand Up @@ -975,7 +982,8 @@ mkNewtype o@Options {..} typName doc instTys ts = \case
} -> do
fieldDocs <- lift $ mapM (getDocWith o) fieldNames
fields <- zipFields o fieldNames constructorFields fieldDocs
matchProxy =<< lift (structExp typName doc instTys dataInterfaces dataProtocols dataAnnotations fields ts makeBase)
deprecatedFieldExps <- lift [e|deprecatedFields|]
matchProxy =<< lift (structExp typName doc instTys dataInterfaces dataProtocols dataAnnotations fields deprecatedFieldExps ts makeBase)
ConstructorInfo
{ constructorFields = [field]
} -> do
Expand Down Expand Up @@ -1004,7 +1012,8 @@ mkProd o@Options {..} typName parentDoc instTys ts = \case
{ constructorVariant = NormalConstructor
, constructorFields = []
} -> do
matchProxy =<< lift (structExp typName parentDoc instTys dataInterfaces dataProtocols dataAnnotations [] ts makeBase)
emptyDeprecatedFieldsExp <- lift [e|[]|]
matchProxy =<< lift (structExp typName parentDoc instTys dataInterfaces dataProtocols dataAnnotations [] emptyDeprecatedFieldsExp ts makeBase)
-- single constructor, non-record (Normal)
ConstructorInfo
{ constructorVariant = NormalConstructor
Expand All @@ -1026,17 +1035,30 @@ mkProd o@Options {..} typName parentDoc instTys ts = \case
} -> do
fieldDocs <- lift $ mapM (getDocWith o) fieldNames
fields <- zipFields o fieldNames constructorFields fieldDocs
matchProxy =<< lift (structExp typName parentDoc instTys dataInterfaces dataProtocols dataAnnotations fields ts makeBase)
deprecatedFieldExp <- lift [e|deprecatedFields|]
matchProxy =<< lift (structExp typName parentDoc instTys dataInterfaces dataProtocols dataAnnotations fields deprecatedFieldExp ts makeBase)

-- | 'strictFields' are required to exist in the record and are always included.
-- 'omitFields' will remove any remaining fields if they are 'Discard'ed.
zipFields :: Options -> [Name] -> [Type] -> [Maybe String] -> MoatM [Exp]
zipFields o ns ts ds = do
let fields = nameStr <$> ns
missingFields = fieldsRequiredByClients o L.\\ fields
if null missingFields
then pure $ catMaybes $ zipWith3 mkField ns ts ds
else throwError $ MissingRequiredFields missingFields
requiredFieldNames = fieldsRequiredByClients o
deprecatedFieldNames = fst <$> deprecatedFields o
missingFields = requiredFieldNames L.\\ fields
missingDeprecatedFields = deprecatedFieldNames L.\\ fields
deprecatedNonRequiredFields = deprecatedFieldNames L.\\ requiredFieldNames
checkMissingFields =
if null missingFields
then pure $ catMaybes $ zipWith3 mkField ns ts ds
else throwError $ MissingRequiredFields missingFields missingDeprecatedFields
case requiredFieldNames of
[] -> checkMissingFields
_requiredFields ->
-- Throw an error if required fields are available but a deprecated field is not included in them
case deprecatedNonRequiredFields of
[] -> checkMissingFields
_xs -> throwError $ MissingDeprecatedRequiredFields deprecatedNonRequiredFields
where
mkField :: Name -> Type -> Maybe String -> Maybe Exp
mkField n t d =
Expand Down Expand Up @@ -1650,12 +1672,14 @@ structExp ::
[Annotation] ->
-- | fields
[Exp] ->
-- | deprecated fields
Exp ->
-- | tags
[Exp] ->
-- | Make base?
(Bool, Maybe MoatType, [Protocol]) ->
Q Exp
structExp name doc tyVars ifaces protos anns fields tags bs = do
structExp name doc tyVars ifaces protos anns fields deprecatedFields tags bs = do
structInterfaces_ <- Syntax.lift ifaces
structAnnotations_ <- Syntax.lift anns
structProtocols_ <- Syntax.lift protos
Expand All @@ -1669,6 +1693,7 @@ structExp name doc tyVars ifaces protos anns fields tags bs = do
, ('structProtocols, structProtocols_)
, ('structAnnotations, structAnnotations_)
, ('structFields, ListE fields)
, ('structDeprecatedFields, deprecatedFields)
, ('structPrivateTypes, ListE [])
, ('structTags, ListE tags)
]
Expand Down
33 changes: 25 additions & 8 deletions src/Moat/Pretty/Kotlin.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ where
import qualified Data.Char as Char
import Data.Functor ((<&>))
import Data.List (intercalate)
import qualified Data.Map as Map
import Data.Maybe (catMaybes, mapMaybe)
import Debug.Trace
import Moat.Pretty.Doc.KDoc
import Moat.Types

Expand All @@ -25,6 +27,7 @@ prettyKotlinData = \case
structInterfaces
structAnnotations
structFields
structDeprecatedFields
indents
MoatEnum {..} ->
prettyEnum
Expand Down Expand Up @@ -64,11 +67,11 @@ prettyTypeDoc indents doc fields =
kdoc = intercalate "\n" (catMaybes [prettyDoc wrap <$> doc, prettyFieldDoc wrap fields])
in prettyDocComment wrap indents kdoc

prettyStructFields :: String -> [Field] -> String
prettyStructFields indents = go
prettyStructFields :: String -> [Field] -> [(String, Maybe String)] -> String
prettyStructFields indents fields deprecatedFields = go fields
where
go [] = ""
go (Field fieldName ty _ : fs) =
deprecatedFieldsMap = Map.fromList deprecatedFields
prettyField (Field fieldName ty _) =
indents
++ "val "
++ fieldName
Expand All @@ -78,7 +81,20 @@ prettyStructFields indents = go
Optional _ -> " = null"
_ -> ""
++ ",\n"
++ go fs
go [] = ""
go (field@(Field fieldName _ _) : fs) =
traceShow deprecatedFieldsMap $
traceShow fieldName $
traceShow fields $
traceShow fs $
case Map.lookup fieldName deprecatedFieldsMap of
Just mComment ->
traceShow "test" $
maybe "" (\comment -> "// " ++ comment ++ "\n") mComment
++ "//"
++ prettyField field
++ go fs
Nothing -> prettyField field ++ go fs

prettyEnumCases :: String -> [EnumCase] -> String
prettyEnumCases indents = go
Expand Down Expand Up @@ -295,7 +311,7 @@ prettyTaggedObject parentName tyVars anns ifaces cases indents SumOfProductEncod
++ "data class "
++ caseTypeHeader caseNm
++ "(\n"
++ prettyStructFields doubleIndents fields
++ prettyStructFields doubleIndents fields []
++ indents
++ ") : "
++ parentTypeHeader
Expand Down Expand Up @@ -350,9 +366,10 @@ prettyStruct ::
-- | fields
[Field] ->
-- | indents
[(String, Maybe String)] ->
String ->
String
prettyStruct name doc tyVars ifaces anns fields indents =
prettyStruct name doc tyVars ifaces anns fields deprecatedFields indents =
prettyTypeDoc noIndent doc fields
++ prettyAnnotations Nothing noIndent anns
++ body
Expand All @@ -368,7 +385,7 @@ prettyStruct name doc tyVars ifaces anns fields indents =
"data class "
++ prettyMoatTypeHeader name (addTyVarBounds tyVars ifaces)
++ "(\n"
++ prettyStructFields indents fields
++ prettyStructFields indents fields deprecatedFields
++ ")"

prettyEnum ::
Expand Down
27 changes: 19 additions & 8 deletions src/Moat/Pretty/Swift.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ where

import Data.Functor ((<&>))
import Data.List (intercalate, nub)
import qualified Data.Map as Map
import Data.Maybe (catMaybes)
import Moat.Pretty.Doc.DocC
import Moat.Types
Expand Down Expand Up @@ -52,7 +53,7 @@ prettySwiftDataWith indent = \case
++ prettyRawValueAndProtocols Nothing structProtocols
++ " {"
++ newlineNonEmpty structFields
++ prettyStructFields indents structFields
++ prettyStructFields indents structFields structDeprecatedFields
++ newlineNonEmpty structPrivateTypes
++ prettyPrivateTypes indents structPrivateTypes
++ prettyTags indents structTags
Expand Down Expand Up @@ -260,19 +261,29 @@ prettyEnumCases indents unknown cases = go cases ++ unknownCase
Just caseNm -> indents ++ "case " ++ caseNm ++ "\n"
Nothing -> ""

prettyStructFields :: String -> [Field] -> String
prettyStructFields indents = go
prettyStructFields :: String -> [Field] -> [(String, Maybe String)] -> String
prettyStructFields indents fields deprecatedFields = go fields
where
go [] = ""
go (Field {..} : fs) =
prettyTypeDoc indents fieldDoc []
++ indents
deprecatedFieldsMap = Map.fromList deprecatedFields
prettyField (Field fieldName fieldType _fieldDoc) =
indents
++ "var "
++ fieldName
++ ": "
++ prettyMoatType fieldType
++ "\n"
++ go fs
go [] = ""
go (field@(Field fieldName _ fieldDoc) : fs) =
case Map.lookup fieldName deprecatedFieldsMap of
Just mComment ->
maybe "" (\comment -> "// " ++ comment ++ "\n") mComment
++ "//"
++ prettyField field
++ go fs
Nothing ->
prettyTypeDoc indents fieldDoc []
++ prettyField field
++ go fs

prettyNewtypeField :: String -> Field -> String -> String
prettyNewtypeField indents (Field alias fieldType _) fieldName =
Expand Down
9 changes: 9 additions & 0 deletions src/Moat/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ data MoatData
-- populated by setting 'makeBase'.
--
-- Only used by the Swift backend.
, structDeprecatedFields :: [(String, Maybe String)]
, structTags :: [MoatType]
-- ^ The tags of the struct. See 'Tag'.
--
Expand Down Expand Up @@ -418,6 +419,13 @@ data Options = Options
--
-- This can be used with @omitFields = const Discard@ to ensure fields are
-- retained for client compatibility.
, deprecatedFields :: [(String, Maybe String)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe not necessary to have the comment as part of this since we could comment on the type directly

Copy link
Contributor Author

Choose a reason for hiding this comment

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

EDIT: @tadfisher noted commenting on the type makes it show up as a doc comment which we dont want since the field is commented out

-- ^ These fields are deprecated for clients and a comment with details about the deprecation
-- deprecated fields are also required in Haskell
--
-- This field will generate a mobile type as a comment instead of actual code
-- and add the specified comment to the resulting type
-- The purpose of this field is to allow fields to be no longer generated
, strictCases :: [String]
-- ^ These enum cases are relied upon and must exist in the sum type.
--
Expand Down Expand Up @@ -574,6 +582,7 @@ defaultOptions =
, omitFields = const Keep
, omitCases = const Keep
, fieldsRequiredByClients = []
, deprecatedFields = []
, strictCases = []
, makeBase = (False, Nothing, [])
, optionalExpand = False
Expand Down
29 changes: 29 additions & 0 deletions test/DeprecatedFieldSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module DeprecatedFieldSpec where

import Common
import Moat
import Test.Hspec
import Test.Hspec.Golden

data Data = Data
{ field0 :: Int
, field1 :: Maybe Int
}

mobileGenWith
( defaultOptions
{ fieldsRequiredByClients = ["field0", "field1"]
, omitFields = const Discard
, deprecatedFields = [("field1", Just "Deprecated since build 500")]
}
)
''Data

spec :: Spec
spec =
fdescribe "stays golden" $ do
let moduleName = "DeprecatedFieldSpec"
it "swift" $
defaultGolden ("swift" <> moduleName) (showSwift @Data)
it "kotlin" $
defaultGolden ("kotlin" <> moduleName) (showKotlin @Data)
Loading