-
Notifications
You must be signed in to change notification settings - Fork 721
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
File type to track the content and direction of files #5105
Conversation
89986e4
to
730c07e
Compare
-- contain and whether it is to be used for reading or writing. | ||
newtype File content (direction :: FileDirection) = File | ||
{ unFile :: FilePath | ||
} deriving newtype (Eq, Ord, Read, Show, IsString, FromJSON, ToJSON) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The File
type has an additional content
type parameter.
46ee037
to
6b12259
Compare
@@ -70,7 +71,7 @@ instance AdjustFilePaths NixServiceOptions where | |||
adjustFilePaths f opts | |||
= opts { | |||
_nix_nodeConfigFile = f <$> _nix_nodeConfigFile opts | |||
, _nix_sigKey = SigningKeyFile . f . unSigningKeyFile $ _nix_sigKey opts | |||
, _nix_sigKey = mapFile f $ _nix_sigKey opts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapFile
works on any File
regardless of file type.
cardano-cli/src/Cardano/CLI/Types.hs
Outdated
deriving newtype (IsString, Show) | ||
data OfSigningKey | ||
|
||
type SigningKeyFile = File OfSigningKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of being a newtype
with its own set of instances we will use a type alias to File
with the file type content
already supplied. This means it's possible to use file operation functions like writeByteStringFile
directly because they accept any content
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid a proliferation of type level tags we can reuse the types in cardano-api
and cardano-cli
with something like the following:
data Any
type SigningKeyFile = File (SigningKey Any)
type VerificationKeyFile = File (VerificationKey Any)
type TxBodyFile = File (TxBody Any)
type TxFile = File (Tx Any)
Let me know what you think
@@ -90,7 +89,7 @@ prop_migrate_legacy_to_nonlegacy_signingkeys = | |||
] | |||
|
|||
eSignKey <- liftIO . runExceptT . readByronSigningKey NonLegacyByronKeyFormat | |||
$ SigningKeyFile nonLegacyKeyFp | |||
$ File nonLegacyKeyFp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use File
and Haskell will infer content = OfSigningKey
.
Alternatively we can use type applications to be more specific: File @OfSigningKey
.
@@ -181,9 +181,9 @@ readLeaderCredentialsSingleton ProtocolFilepaths {shelleyKESFile = Nothing} = | |||
left KESKeyNotSpecified | |||
|
|||
opCertKesKeyCheck | |||
:: FilePath | |||
:: File () In |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use ()
for when we previously used FilePath
.
This means we're not tracking the content type of the file yet. We can add this later.
writeByteStringFile fp bs = runExceptT $ | ||
handleIOExceptT (FileIOError fp) $ BS.writeFile fp bs | ||
handleIOExceptT (FileIOError (unFile fp)) $ BS.writeFile (unFile fp) bs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unFile
is an escape hatch to get the FilePath
within. We use it to implement file operations like writeByteStringFile
.
=> MonadIO m | ||
=> File content Out | ||
-> ByteString | ||
-> m (Either (FileError ()) ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functions can work on all files by leaving content
unspecified.
coldSK = SigningKeyFile $ dir </> "delegate" ++ strIndex ++ ".skey" | ||
opCertCtr = OpCertCounterFile $ dir </> "delegate" ++ strIndex ++ ".counter" | ||
kesVK = File @OfVerificationKey $ dir </> "delegate" ++ strIndex ++ ".kes.vkey" | ||
coldSK = File @OfSigningKey $ dir </> "delegate" ++ strIndex ++ ".skey" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coldSK
is a signing key file that is both written to and read from in the same command. It is therefore annotated as InOut
. This type is inferred, but can be made explicit with type application of @InOut
.
(VerificationKeyFilePath kesVK) | ||
coldSK | ||
(VerificationKeyFilePath (onlyIn kesVK)) | ||
(onlyIn coldSK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To use an InOut
file for an In
operation, we can cast it. This works on all file types regardless of content type.
coldSK | ||
opCertCtr | ||
(File @OfVerificationKey $ dir </> "delegate" ++ strIndex ++ ".vkey") | ||
(onlyOut coldSK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To use an InOut
file for an Out
operation, we can cast it. This works on all file types regardless of content type.
@@ -1279,7 +1279,7 @@ runQueryLeadershipSchedule (AnyConsensusModeParams cModeParams) network | |||
|
|||
case mJsonOutputFile of | |||
Nothing -> liftIO $ printLeadershipScheduleAsText schedule eInfo (SystemStart $ sgSystemStart shelleyGenesis) | |||
Just (OutputFile jsonOutputFile) -> | |||
Just (File jsonOutputFile) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are parts of the code where we still unwrap the FilePath
and use that directly. All these cases can be converted to use the type-safe functions like writeLazyByteString
instead. It isn't done here to keep this PR as small as possible.
pByronKeyFile = | ||
(ASigningKeyFile <$> pByronSigningKeyFile) | ||
<|> (AVerificationKeyFile <$> pByronVerificationKeyFile) | ||
|
||
pByronSigningKeyFile :: Parser SigningKeyFile | ||
pByronSigningKeyFile :: Parser (SigningKeyFile In) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can now be explicit in the types for parsers to say this is meant for input only. If we use it for Out
or for InOut
or for the wrong content type, we get a type error.
70f40bc
to
1937111
Compare
1937111
to
75ec1bc
Compare
|
||
-- | A file path with additional type information to indicate what the file is meant to | ||
-- contain and whether it is to be used for reading or writing. | ||
newtype File content (direction :: FileDirection) = File |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
direction
could also be represented as a type-level list [FileDirection]
. This way functions requiring file for writing for example could have a following signature:
writeByteStringOutput :: ()
=> MonadIO m
=> Out `CouldBeAnyOf` directions
=> Maybe (File content directions)
-> ByteString
-> m (Either (FileError ()) ())
this way conversion of InOut
to In
or Out
would be not necessary and onlyOut
functions wouldn't be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea, but it can be a separate PR.
I think I'm missing the value of having |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but just one consideration: #5105 (comment)
cardano-cli/src/Cardano/CLI/Types.hs
Outdated
deriving newtype (IsString, Show) | ||
data OfSigningKey | ||
|
||
type SigningKeyFile = File OfSigningKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid a proliferation of type level tags we can reuse the types in cardano-api
and cardano-cli
with something like the following:
data Any
type SigningKeyFile = File (SigningKey Any)
type VerificationKeyFile = File (VerificationKey Any)
type TxBodyFile = File (TxBody Any)
type TxFile = File (Tx Any)
Let me know what you think
e1e75c9
to
6a46d9a
Compare
I've used this instead:
|
The same is true of the The benefits are the same also. If a function takes two different file types you can't accidentally put them in the wrong order. Having a There's lots of examples of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @newhoggy !
6a46d9a
to
20a31ec
Compare
This is an alternate design to #5017