Skip to content

Commit

Permalink
Add deprecated fields support
Browse files Browse the repository at this point in the history
  • Loading branch information
josephsumabat committed Oct 10, 2024
1 parent 1b4217b commit f5abee8
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 27 deletions.
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)]
-- ^ 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)

0 comments on commit f5abee8

Please sign in to comment.