Skip to content

Commit

Permalink
simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
fubhy committed Sep 23, 2023
1 parent 0cffde1 commit f88d5bc
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 70 deletions.
8 changes: 6 additions & 2 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
{
"name": "cli",
"private": true,
"devDependencies": {
"@types/node": "^18.11.11"
"scripts": {
"dev": "tsx --watch"
},
"dependencies": {
"@effect/cli": "^0.12.0",
"@effect/platform": "^0.16.1",
"@effect/platform-node": "^0.17.0",
"@effect/schema": "^0.36.5",
"effect": "2.0.0-next.34"
},
"devDependencies": {
"@types/node": "^18.11.11",
"tsx": "^3.12.10"
}
}
38 changes: 0 additions & 38 deletions cli/src/index.ts

This file was deleted.

79 changes: 49 additions & 30 deletions cli/src/wttr.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Effect, Data, Context, Layer, Option } from 'effect'
import * as Http from '@effect/platform/HttpClient'
import { Effect, Option, Cause } from 'effect'
import * as Http from '@effect/platform-node/HttpClient'
import * as Node from '@effect/platform-node/Runtime'
import * as Command from '@effect/cli/Command'
import * as Cli from '@effect/cli/CliApp'
import * as Options from '@effect/cli/Options'
import * as Args from '@effect/cli/Args'
import * as Console from '@effect/cli/Console'
import * as Schema from '@effect/schema/Schema'

type WttrWeather = Schema.Schema.To<typeof WttrWeatherSchema>
const WttrWeatherSchema = Schema.struct({
date: Schema.dateFromString(Schema.string),
avgtempC: Schema.numberFromString(Schema.string),
Expand All @@ -19,33 +24,47 @@ const WttrResponseSchema = Schema.struct({
weather: Schema.array(WttrWeatherSchema)
})

class WttrError extends Data.TaggedClass('WttrError')<{
cause: unknown
}> {}
const cli = Cli.make({
name: 'Weather',
version: '1.2.3',
command: Command.make('weather', {
// Accept 0 or 1 arguments for the location and convert it to a `Option<string>`.
args: Args.between(Args.text({ name: 'location' }), 0, 1).pipe(Args.map(Option.fromIterable)),
options: Options.all({
url: Options.withDefault(Options.text('url'), 'https://wttr.in'),
}),
})
})

export const Wttr = Context.Tag<Wttr>()
export interface Wttr {
readonly getWeather: (location: Option.Option<string>) => Effect.Effect<never, WttrError, ReadonlyArray<WttrWeather>>
}
Node.runMain(Effect.sync(() => process.argv.slice(2)).pipe(
Effect.flatMap((args) =>
Cli.run(cli, args, ({ options: { url }, args: location }) => {
const program = Effect.gen(function* ($) {
const defaultClient = yield* $(Http.client.Client)
const wttrClient = defaultClient.pipe(
// Always prepend the service url to all requests. This makes it more convenient to use the client.
Http.client.mapRequest(Http.request.prependUrl(url)),
// Tell the wttr.in service to return json.
Http.client.mapRequest(Http.request.appendUrlParam('format', 'j1')),
// Only accept status responses with a `2xx` status code. Fail otherwise.
Http.client.filterStatusOk,
// Decode all responses using the `WttrResponseSchema` schema.
Http.client.mapEffect(Http.response.schemaBodyJson(WttrResponseSchema)),
);

export const makeLayer = (url: string) => Layer.effect(Wttr, Effect.gen(function* ($) {
const defaultClient = yield* $(Http.client.Client)
const wttrClient = defaultClient.pipe(
Http.client.mapRequest(Http.request.prependUrl(url)),
Http.client.mapRequest(Http.request.appendUrlParam('format', 'j1')),
Http.client.filterStatusOk,
Http.client.mapEffect(Http.response.schemaBodyJson(WttrResponseSchema)),
Http.client.catchTags({
'RequestError': (cause) => Effect.fail(new WttrError({ cause })),
'ResponseError': (cause) => Effect.fail(new WttrError({ cause })),
'ParseError': (cause) => Effect.fail(new WttrError({ cause })),
}),
)
// Request the weather for the provided location or the current location if none was provided.
const wttrResponse = yield* $(Option.match(location, {
onNone: () => Http.request.get('/'),
onSome: (_) => Http.request.get(`/${encodeURIComponent(_)}`),
}).pipe(wttrClient, Effect.map(_ => _.weather)));

return Wttr.of({
getWeather: (location) => Option.match(location, {
onNone: () => Http.request.get('/'),
onSome: (_) => Http.request.get(`/${encodeURIComponent(_)}`),
}).pipe(wttrClient, Effect.map(_ => _.weather)),
})
}))
// TODO: Pretty print the weather.
yield* $(Effect.log(wttrResponse));
});

return Effect.provideLayer(program, Http.client.layer)
})
),
Effect.provideLayer(Console.layer),
Effect.tapErrorCause((_) => Effect.logError(Cause.pretty(_)))
))
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f88d5bc

Please sign in to comment.