-
Notifications
You must be signed in to change notification settings - Fork 46
Code idiom review #159
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
Comments
Hey, I'm not the creator nor a contributor of this project, but your question is interesting so here is how I would have implemented it. Yes you're right, a First let's define an environment which will be used in the class SshContext {
final SshClient client;
final Logger logger;
SshContext({required this.client, required this.logger});
} The enum LogLevel { debug, info, error }
abstract class Logger {
final LogLevel level;
Logger({required this.level});
Unit logInfo(String message);
Unit logDebug(String message);
Unit logError(String message);
}
class ConsoleLogger extends Logger {
ConsoleLogger({required super.level});
Unit _log(LogLevel level, String message) {
if (level.index >= this.level.index) {
print('[${level.name.toString().toUpperCase()}] $message');
}
return unit;
}
@override
Unit logDebug(String message) => _log(LogLevel.debug, message);
@override
Unit logError(String message) => _log(LogLevel.error, message);
@override
Unit logInfo(String message) => _log(LogLevel.info, message);
} Then let's define a function which:
typedef SshCommandResult<R> = ReaderTaskEither<SshContext, SshError, R>;
SshCommandResult<R> executeCommand<R>(
String command, {
required R Function(bool) onResult,
required SshError Function(Object? error, StackTrace? stackTrace) onError,
}) {
return ReaderTaskEither.tryCatch((SshContext context) async {
final result = await context.client.executeCommand(command);
return onResult(result);
}, (error, stack) {
return switch (error) {
SshError() => error,
_ => onError(error, stack),
};
}).logDebug('Executing command: "$command"');
} Note that I have define the The extension SshCommandResultX<R> on SshCommandResult<R> {
SshCommandResult<R> logInfo(String message) {
return SshCommandResult<R>((context) {
context.logger.logInfo(message);
return run(context);
});
}
SshCommandResult<R> logDebug(String message) {
return SshCommandResult<R>((context) {
context.logger.logDebug(message);
return run(context);
});
}
} Note that these methods return a Now we can define a SshCommandResult<bool> connectToServer() => ReaderTaskEither<SshContext, SshError, bool>.tryCatch(
(context) async {
final result = await context.client.connect();
return result ? result : throw SshConnectError(context.client.host, context.client.username);
},
(error, stack) {
return SshConnectError('', '');
},
).logInfo('Connecting to the server');
SshCommandResult<Unit> disconnectFromServer() => executeCommand(
'exit',
onResult: throwIfCommandFailed(SshDisconnectError()),
onError: (_, __) => SshDisconnectError(),
).logInfo('Disconnecting from the server');
SshCommandResult<Unit> stopService(String serviceName) => executeCommand(
'systemctl stop $serviceName',
onResult: throwIfCommandFailed(ServiceStopError()),
onError: (_, __) => ServiceStopError(),
).logInfo('Stopping service $serviceName');
SshCommandResult<Unit> restartService(String serviceName) => executeCommand(
'systemctl restart $serviceName',
onResult: throwIfCommandFailed(ServiceRestartError()),
onError: (_, __) => ServiceRestartError(),
).logInfo('Restarting service $serviceName');
SshCommandResult<Unit> reloadDaemon() => executeCommand(
'systemctl daemon-reload',
onResult: throwIfCommandFailed(ReloadDaemonError()),
onError: (_, __) => ServiceRestartError(),
).logInfo('Reloading daemon');
SshCommandResult<bool> doesFolderExists(String folderPath) => executeCommand(
'test -d $folderPath',
onResult: (result) => false,
onError: (_, __) => DoesFolderExistError(),
).logInfo('Does folder $folderPath exists ?');
SshCommandResult<Unit> createFolder(String folderPath) => executeCommand(
'mkdir -p $folderPath',
onResult: throwIfCommandFailed(CreateFolderError(folderPath)),
onError: (_, __) => CreateFolderError(folderPath),
).logInfo('Creating folder $folderPath');
SshCommandResult<Unit> createFolderIfNotExist(String folder) => doesFolderExists(folder)
.flatMap(
(exist) => exist ? ReaderTaskEither.of(unit) : createFolder(folder),
)
.logInfo('Creating folder if not exists $folder'); Note that the An finally the Future<void> main(List<String> arguments) async {
final logger = ConsoleLogger(level: LogLevel.info);
connectToServer()
.andThen(() => createFolderIfNotExist('/Users/eric.taix/Projects/perso/ssh_client'))
.andThen(() => stopService('service1'))
.andThen(() => restartService('service2'))
.andThen(() => disconnectFromServer())
.match(
(message) => logger.logInfo(message.toString()),
(_) => logger.logError('Job finished'),
)
.run(SshContext(
client: SshClient('127.0.0.1', 'eric', 'password'),
logger: logger,
));
}
} As you don't use the returned value, you can use the I have also defined an utility function which throws a Unit Function(bool) throwIfCommandFailed(SshError error) => (commandResult) => commandResult ? unit : throw error; Of course I didn't implemented all commands, but I think it's enough to understand the concept ;-) |
@eric-taix thank you fro this very detailed answer and my apologies for the delay in mine. Your example really made it click for me. Especially how you setup everything with a few utility functions (which I have been missing while trying to internalize some basic concepts on how to do functional prog in Dart).
I'm still more partial to the Do notation instead of PS: It Always feel nice seeing the French tech community being so helpful ;-) |
@Driky Glad to see that my code can help you. You're right, |
Hello, I just started using this wonderful package to help me handle and simplify a piece of code with lots of error handling.
If possible could someone give me a review regarding my usage of
fpdart
in the following code extracts ?The following pieces of code are used to automate the deployment of CI artifacts on prototypes. This is done by connecting to the device through SSH. A few details that comes up:
doSpoofService
isspoofService
written in Do.depositArtifact
would be my next candidate for that treatment.) way more compact and readable but I do not know if I'm using it right.Reader
to handle this properly ?Any comment is welcome, and I wouldn't mind documenting whatever discussion comes out of this and open a PR to help improve the documentation.
The interface used by the following classes , nothing very interesting here for completion purpose:
An abstract child of the base class, used to provide common functionnalities to the final implementations:
Of final implementation that only reuse the function provided by the abstract class:
Finnally the code is executed like this:
The text was updated successfully, but these errors were encountered: