effect-tsでHTTPリクエスト

HTTPリクエストする場合の方法のメモ

* リクエストデータのチェック
* レスポンスデータのチェック
* get, post
あたりのみの簡易版

# effect-ts

https://effect.website/

## 共通処理

<details><summary>共通処理コード</summary>

<div>

```common.ts
import axios from 'axios'
import { Context, Data, Effect, Either, Layer, ManagedRuntime, Option } from 'effect'

export class ApiCallNg extends Data.TaggedError('API呼び出し失敗')<{ error: any }> {}
export class CheckRequestDataNg extends Data.TaggedError('リクエストデータチェックNG')<{
  error: any
}> {}
export class CheckResponseDataNg extends Data.TaggedError('レスポンスデータ変換失敗')<{
  error: any
}> {}

export class HttpRequestService extends Context.Tag('HttpRequestService')<
  HttpRequestService,
  {
    readonly get: (url: string) => Effect.Effect<Either.Either<any, ApiCallNg>>
    readonly post: (url: string, data: any) => Effect.Effect<Either.Either<any, ApiCallNg>>
  }
>() {
  static readonly Axios = Layer.succeed(HttpRequestService, {
    get: function (url: string) {
      return Effect.gen(function* () {
        const effect = Effect.tryPromise({
          try: () => axios.get(url).then((response) => response.data),
          catch: (error) => new ApiCallNg({ error })
        })
        const either = yield* Effect.either(effect)
        return either
      })
    },
    post: function (url: string, request: any) {
      return Effect.gen(function* () {
        const effect = Effect.tryPromise({
          try: () => axios.post(url, request).then((response) => response.data),
          catch: (error) => new ApiCallNg({ error })
        })
        const either = yield* Effect.either(effect)
        return either
      })
    }
  })
}

export class UrlService extends Context.Tag('UrlService')<
  UrlService,
  {
    readonly url: string
    readonly requestData?: any
  }
>() {}

export class RequestDataCheckService extends Context.Tag('RequestDataCheckService')<
  RequestDataCheckService,
  {
    readonly checkRequestData: (data: any) => Either.Either<any, CheckRequestDataNg>
  }
>() {}

export class ResponseDataCheckService extends Context.Tag('ResponseDataCheckService')<
  ResponseDataCheckService,
  {
    readonly checkResponseData: (data: any) => Either.Either<any, CheckResponseDataNg>
  }
>() {}

const getRuntime = (
  httpService: Layer.Layer<HttpRequestService, never, never>,
  urlService: Layer.Layer<UrlService, never, never>,
  checkRequestService?: Layer.Layer<RequestDataCheckService, never, never>,
  checkResponseService?: Layer.Layer<ResponseDataCheckService, never, never>
) => {
  const array: any = [urlService, httpService]
  if (checkRequestService) {
    array.push(checkRequestService)
  }
  if (checkResponseService) {
    array.push(checkResponseService)
  }

  const layers = Layer.mergeAll(...(array as any))
  const runtime = ManagedRuntime.make(layers as any)
  return runtime
}

const AxiosRuntime = (
  urlService: Layer.Layer<UrlService, never, never>,
  checkRequestService?: Layer.Layer<RequestDataCheckService, never, never>,
  checkResponseService?: Layer.Layer<ResponseDataCheckService, never, never>
) => getRuntime(HttpRequestService.Axios, urlService, checkRequestService, checkResponseService)

function GetApi<A>() {
  return Effect.gen(function* () {
    const urlService = yield* UrlService
    const httpService = yield* HttpRequestService
    const checkService = yield* ResponseDataCheckService

    const apiResult = yield* httpService.get(urlService.url)
    const dataResult = Either.match(apiResult, {
      onLeft: (left) => Either.left(left),
      onRight: (data) =>
        checkService.checkResponseData(data) as Either.Either<A, CheckResponseDataNg>
    })
    return dataResult as Either.Either<A, ApiCallNg | CheckResponseDataNg>
  })
}

function PostApi<A>() {
  return Effect.gen(function* () {
    const urlService = yield* UrlService
    const httpService = yield* HttpRequestService
    const option_CheckRequestService = yield* Effect.serviceOption(RequestDataCheckService)
    const option_CheckResponseService = yield* Effect.serviceOption(ResponseDataCheckService)

    type ngType = ApiCallNg | CheckRequestDataNg | CheckResponseDataNg

    const requestData = Option.isNone(option_CheckRequestService)
      ? undefined
      : option_CheckRequestService.value.checkRequestData(urlService.requestData)
    if (requestData !== undefined && Either.isLeft(requestData)) {
      return requestData as Either.Either<A, ngType>
    }

    const apiResult = yield* httpService.post(urlService.url, requestData)
    const dataResult = Either.match(apiResult, {
      onLeft: (left) => Either.left(left),
      onRight: (data: A) =>
        Option.isNone(option_CheckResponseService)
          ? Either.right(data)
          : option_CheckResponseService.value.checkResponseData(data)
    })
    return dataResult as Either.Either<A, ngType>
  })
}

export default { GetApi, PostApi, AxiosRuntime, getRuntime }
```

</div>
</details>


## getを使用する : api_123

<details><summary>api_123実装コード</summary>

<div>

```api_123.ts
import { Either, Layer, Schema } from 'effect'
import { CheckResponseDataNg, ResponseDataCheckService, UrlService } from './common'
import common from './common'

const api_123 = Layer.succeed(UrlService, {
  url: 'https://example.com/123'
})

const response_api_123_schema = Schema.Struct({
  test1: Schema.String,
  test2: Schema.Number
})

export type response_api_123_type = Schema.Schema.Type<typeof response_api_123_schema>

const check_response_api_123 = Layer.succeed(ResponseDataCheckService, {
  checkResponseData: (data: any) => {
    const checkResult = Schema.decodeEither(response_api_123_schema)(data)
    const checkResultMapLeft = Either.mapLeft(
      checkResult,
      (error) => new CheckResponseDataNg({ error })
    )
    return checkResultMapLeft
  }
})

export const call_api_123 = (
  runtime = common.AxiosRuntime,
  api = api_123,
  check_request = undefined,
  check_response = check_response_api_123
) => runtime(api, check_request, check_response).runPromise(common.GetApi<response_api_123_type>())

export default {
  response_api_123_schema,
  check_response_api_123
}
```

</div>
</details>

### テストコード

```ts
import { Effect, Either, Layer } from 'effect'
import { describe, it } from 'vitest'
import {
  ApiCallNg,
  HttpRequestService,
  RequestDataCheckService,
  ResponseDataCheckService,
  UrlService
} from '../common'
import common from '../common'
import { call_api_123, type response_api_123_type } from '../api_123'

// テストでのモック設定
function mock_post() {
  return Effect.gen(function* () {
    yield* Effect.log('mock post')
    return Either.right(undefined)
  })
}

describe('HTTP Request サンプル - api_123 - get', () => {
  // モック設定
  const TestUrlService = Layer.succeed(HttpRequestService, {
    get: function (url: string) {
      return Effect.gen(function* () {
        yield* Effect.log('test get')

        switch (url) {
          case 'ApiCallNg': {
            const error = new ApiCallNg({ error: 'test error' })

            const response = Either.left(error)
            return response
          }
          case 'ResponseDataNg': {
            const testResponseData = {
              test: 'ng response data'
            }

            const response = Either.right(testResponseData)
            return response
          }
          case 'OK':
          case 'https://example.com/123':
          default: {
            const testResponseData: response_api_123_type = {
              test1: 'test string',
              test2: 123
            }

            const response = Either.right(testResponseData)
            return response
          }
        }
      })
    },
    post: mock_post
  })
  const TestRuntime = (
    urlService: Layer.Layer<UrlService, never, never>,
    checkRequestService?: Layer.Layer<RequestDataCheckService, never, never>,
    checkResponseService?: Layer.Layer<ResponseDataCheckService, never, never>
  ) => common.getRuntime(TestUrlService, urlService, checkRequestService, checkResponseService)

  it('実際の呼び出し', async () => {
    const result = await call_api_123()
    console.log(result)
  })
  it('モックテスト時の呼び出し - ok', async () => {
    const result = await call_api_123(TestRuntime)
    console.log(result)

    // OK処理サンプル
    if (Either.isRight(result)) {
      console.log('処理成功時の後続処理')
    }
  })
  it('モックテスト時の呼び出し - ng - API呼び出し失敗', async () => {
    const ng_url = Layer.succeed(UrlService, {
      url: 'ApiCallNg'
    })
    const result = await call_api_123(TestRuntime, ng_url)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'API呼び出し失敗') {
        console.log('API呼び出し失敗のリカバリ処理')
      }
    }
  })
  it('モックテスト時の呼び出し - ng - レスポンスデータ変換失敗', async () => {
    const ng_url = Layer.succeed(UrlService, {
      url: 'ResponseDataNg'
    })
    const result = await call_api_123(TestRuntime, ng_url)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'レスポンスデータ変換失敗') {
        console.log('レスポンスデータ変換失敗のリカバリ処理')
      }
    }
  })
})
```

実行結果
```ts
Error: Cross origin http://localhost:3000 forbidden
    at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:63:19)
    at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
    at receiveResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:794:19)
    at Request.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:658:43)
    at Request.emit (node:events:517:28)
    at Request._processResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:235:12)
    at ClientRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:107:12)
    at Object.onceWrapper (node:events:632:26)
    at ClientRequest.emit (node:events:517:28)
    at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:700:27) undefined
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: AxiosError: Network Error
        at XMLHttpRequest.handleError (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/adapters/xhr.js:110:14)
        at XMLHttpRequest.invokeTheCallbackFunction (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\generated\EventHandlerNonNull.js:14:28)
        at XMLHttpRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\create-event-accessor.js:35:32)
        at innerInvokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:350:25)
        at invokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:286:3)
        at XMLHttpRequestImpl._dispatch (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:233:9)
        at fireAnEvent (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\events.js:18:36)
        at requestErrorSteps (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:131:3)
        at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:60:3)
        at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
        at Axios.request (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/core/Axios.js:45:41)
        at processTicksAndRejections (node:internal/process/task_queues:95:5) {
      code: 'ERR_NETWORK',
      config: [Object],
      request: XMLHttpRequest {}
    },
    _tag: 'API呼び出し失敗'
  }
}
timestamp=2025-03-20T06:15:09.627Z level=INFO fiber=#10 message="test get"
{
  _id: 'Either',
  _tag: 'Right',
  right: { test1: 'test string', test2: 123 }
}
処理成功時の後続処理
timestamp=2025-03-20T06:15:09.638Z level=INFO fiber=#19 message="test get"
{
  _id: 'Either',
  _tag: 'Left',
  left: { error: 'test error', _tag: 'API呼び出し失敗' }
}
API呼び出し失敗のリカバリ処理
timestamp=2025-03-20T06:15:09.646Z level=INFO fiber=#28 message="test get"
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: {
      _id: 'ParseError',
      message: '{ readonly test1: string; readonly test2: number }\n' +
        '└─ ["test1"]\n' +
        '   └─ is missing'
    },
    _tag: 'レスポンスデータ変換失敗'
  }
}
レスポンスデータ変換失敗のリカバリ処理
```


## postを使用する : api_456

<details><summary>api_456実装コード</summary>

<div>

```api_456.ts
import { Layer, Schema } from 'effect'
import { UrlService } from './common'
import common from './common'
import api_123 from './api_123'

const api_456 = Layer.succeed(UrlService, {
  url: 'https://example.com/456'
})

const response_api_456_schema = api_123.response_api_123_schema

export type response_api_456_type = Schema.Schema.Type<typeof response_api_456_schema>

const check_response_api_456 = api_123.check_response_api_123

export const call_api_456 = (
  runtime = common.AxiosRuntime,
  api = api_456,
  check_request = undefined,
  check_response = check_response_api_456
) => runtime(api, check_request, check_response).runPromise(common.PostApi<response_api_456_type>())
```

</div>
</details>

### テストコード

```ts
import { Effect, Either, Layer } from 'effect'
import { describe, it } from 'vitest'
import {
  ApiCallNg,
  HttpRequestService,
  RequestDataCheckService,
  ResponseDataCheckService,
  UrlService
} from '../common'
import common from '../common'
import { call_api_456, type response_api_456_type } from '../api_456'

// テストでのモック設定
function mock_get() {
  return Effect.gen(function* () {
    yield* Effect.log('mock get')
    return Either.right(undefined)
  })
}

describe('HTTP Request サンプル - api_456 - post', () => {
  // モック設定
  const TestUrlService = Layer.succeed(HttpRequestService, {
    get: mock_get,
    post: function (url: string) {
      return Effect.gen(function* () {
        yield* Effect.log('test post')

        switch (url) {
          case 'ApiCallNg': {
            const error = new ApiCallNg({ error: 'test error' })

            const response = Either.left(error)
            return response
          }
          case 'ResponseDataNg': {
            const testResponseData = {
              test: 'ng response data'
            }

            const response = Either.right(testResponseData)
            return response
          }
          case 'OK':
          case 'https://example.com/456':
          default: {
            const testResponseData: response_api_456_type = {
              test1: 'test string',
              test2: 456
            }

            const response = Either.right(testResponseData)
            return response
          }
        }
      })
    }
  })
  const TestRuntime = (
    urlService: Layer.Layer<UrlService, never, never>,
    checkRequestService?: Layer.Layer<RequestDataCheckService, never, never>,
    checkResponseService?: Layer.Layer<ResponseDataCheckService, never, never>
  ) => common.getRuntime(TestUrlService, urlService, checkRequestService, checkResponseService)

  it('実際の呼び出し', async () => {
    const result = await call_api_456()
    console.log(result)
  })
  it('モックテスト時の呼び出し - ok', async () => {
    const result = await call_api_456(TestRuntime)
    console.log(result)

    // OK処理サンプル
    if (Either.isRight(result)) {
      console.log('処理成功時の後続処理')
    }
  })
  it('モックテスト時の呼び出し - ng - API呼び出し失敗', async () => {
    const ng_url = Layer.succeed(UrlService, {
      url: 'ApiCallNg'
    })
    const result = await call_api_456(TestRuntime, ng_url)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'API呼び出し失敗') {
        console.log('API呼び出し失敗のリカバリ処理')
      }
    }
  })
  it('モックテスト時の呼び出し - ng - レスポンスデータ変換失敗', async () => {
    const ng_url = Layer.succeed(UrlService, {
      url: 'ResponseDataNg'
    })
    const result = await call_api_456(TestRuntime, ng_url)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'レスポンスデータ変換失敗') {
        console.log('レスポンスデータ変換失敗のリカバリ処理')
      }
    }
  })
})
```

実行結果
```ts
Error: Cross origin http://localhost:3000 forbidden
    at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:63:19)
    at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
    at receiveResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:794:19)
    at Request.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:658:43)
    at Request.emit (node:events:517:28)
    at Request._processResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:235:12)
    at ClientRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:107:12)
    at Object.onceWrapper (node:events:632:26)
    at ClientRequest.emit (node:events:517:28)
    at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:700:27) undefined
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: AxiosError: Network Error
        at XMLHttpRequest.handleError (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/adapters/xhr.js:110:14)
        at XMLHttpRequest.invokeTheCallbackFunction (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\generated\EventHandlerNonNull.js:14:28)
        at XMLHttpRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\create-event-accessor.js:35:32)
        at innerInvokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:350:25)
        at invokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:286:3)
        at XMLHttpRequestImpl._dispatch (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:233:9)
        at fireAnEvent (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\events.js:18:36)
        at requestErrorSteps (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:131:3)
        at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:60:3)
        at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
        at Axios.request (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/core/Axios.js:45:41)
        at processTicksAndRejections (node:internal/process/task_queues:95:5) {
      code: 'ERR_NETWORK',
      config: [Object],
      request: XMLHttpRequest {}
    },
    _tag: 'API呼び出し失敗'
  }
}
timestamp=2025-03-20T06:15:38.901Z level=INFO fiber=#10 message="test post"
{
  _id: 'Either',
  _tag: 'Right',
  right: { test1: 'test string', test2: 456 }
}
処理成功時の後続処理
timestamp=2025-03-20T06:15:38.909Z level=INFO fiber=#19 message="test post"
{
  _id: 'Either',
  _tag: 'Left',
  left: { error: 'test error', _tag: 'API呼び出し失敗' }
}
API呼び出し失敗のリカバリ処理
timestamp=2025-03-20T06:15:38.914Z level=INFO fiber=#28 message="test post"
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: {
      _id: 'ParseError',
      message: '{ readonly test1: string; readonly test2: number }\n' +
        '└─ ["test1"]\n' +
        '   └─ is missing'
    },
    _tag: 'レスポンスデータ変換失敗'
  }
}
レスポンスデータ変換失敗のリカバリ処理
```


## postとリクエストデータを使用する : api_789

<details><summary>api_789実装コード</summary>

<div>

```api_789.ts
import { Either, Layer, Schema } from 'effect'
import { CheckRequestDataNg, RequestDataCheckService, UrlService } from './common'
import common from './common'

const api_789 = (requestData: request_api_789_type) =>
  Layer.succeed(UrlService, {
    url: 'https://example.com/789',
    requestData
  })

const request_api_789_schema = Schema.Struct({
  name: Schema.String,
  age: Schema.Number
})

export type request_api_789_type = Schema.Schema.Type<typeof request_api_789_schema>

const check_request_api_789 = Layer.succeed(RequestDataCheckService, {
  checkRequestData: (data: any) => {
    const checkResult = Schema.encodeEither(request_api_789_schema)(data)
    const checkResultMapLeft = Either.mapLeft(
      checkResult,
      (error) => new CheckRequestDataNg({ error })
    )
    return checkResultMapLeft
  }
})

const base_call_api_789 =
  (requestData: request_api_789_type) =>
  (
    runtime = common.AxiosRuntime,
    api = api_789(requestData),
    check_request = check_request_api_789,
    check_response = undefined
  ) =>
    runtime(api, check_request, check_response).runPromise(common.PostApi())

export const call_api_789 = (requestData: request_api_789_type) => base_call_api_789(requestData)()

export default {
  base_call_api_789
}
```

</div>
</details>

### テストコード

```ts
import { Effect, Either, Layer } from 'effect'
import { describe, it } from 'vitest'
import {
  ApiCallNg,
  HttpRequestService,
  RequestDataCheckService,
  ResponseDataCheckService,
  UrlService
} from '../common'
import common from '../common'
import { call_api_789, type request_api_789_type } from '../api_789'
import api_789 from '../api_789'

// テストでのモック設定
function mock_get() {
  return Effect.gen(function* () {
    yield* Effect.log('mock get')
    return Either.right(undefined)
  })
}

describe('HTTP Request サンプル - api_789 - post + data', () => {
  // モック設定
  const TestLayer = Layer.succeed(HttpRequestService, {
    get: mock_get,
    post: function (url: string) {
      return Effect.gen(function* () {
        yield* Effect.log('test post')

        switch (url) {
          case 'ApiCallNg': {
            const error = new ApiCallNg({ error: 'test error' })

            const response = Either.left(error)
            return response
          }
          case 'OK':
          case 'https://example.com/789':
          default: {
            const response = Either.right(undefined)
            return response
          }
        }
      })
    }
  })
  const TestRuntime = (
    urlService: Layer.Layer<UrlService, never, never>,
    checkRequestService?: Layer.Layer<RequestDataCheckService, never, never>,
    checkResponseService?: Layer.Layer<ResponseDataCheckService, never, never>
  ) => common.getRuntime(TestLayer, urlService, checkRequestService, checkResponseService)

  it('実際の呼び出し', async () => {
    const request: request_api_789_type = {
      name: 'test name',
      age: 789
    }
    const result = await call_api_789(request)
    console.log(result)
  })
  it('モックテスト時の呼び出し - ok', async () => {
    const request: request_api_789_type = {
      name: 'test name',
      age: 789
    }
    const result = await api_789.base_call_api_789(request)(TestRuntime)
    console.log(result)

    // OK処理サンプル
    if (Either.isRight(result)) {
      console.log('処理成功時の後続処理')
    }
  })
  it('モックテスト時の呼び出し - ng - API呼び出し失敗', async () => {
    const request: request_api_789_type = {
      name: 'test name',
      age: 789
    }
    const ng_url = Layer.succeed(UrlService, {
      url: 'ApiCallNg',
      requestData: request
    })
    const result = await api_789.base_call_api_789(request)(TestRuntime, ng_url)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'API呼び出し失敗') {
        console.log('API呼び出し失敗のリカバリ処理')
      }
    }
  })
  it('モックテスト時の呼び出し - ng - リクエストデータチェックNG', async () => {
    const request: any = {
      test1: 'test name',
      test2: 789
    }
    const result = await api_789.base_call_api_789(request)(TestRuntime)
    console.log(result)

    // NG処理サンプル
    if (Either.isLeft(result)) {
      if (result.left._tag === 'リクエストデータチェックNG') {
        console.log('リクエストデータチェックNGのリカバリ処理')
      }
    }
  })
})
```

実行結果
```ts
Error: Cross origin http://localhost:3000 forbidden
    at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:63:19)
    at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
    at receiveResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:794:19)
    at Request.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:658:43)
    at Request.emit (node:events:517:28)
    at Request._processResponse (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:235:12)
    at ClientRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\http-request.js:107:12)
    at Object.onceWrapper (node:events:632:26)
    at ClientRequest.emit (node:events:529:35)
    at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:700:27) undefined
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: AxiosError: Network Error
        at XMLHttpRequest.handleError (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/adapters/xhr.js:110:14)
        at XMLHttpRequest.invokeTheCallbackFunction (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\generated\EventHandlerNonNull.js:14:28)
        at XMLHttpRequest.<anonymous> (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\create-event-accessor.js:35:32)
        at innerInvokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:350:25)
        at invokeEventListeners (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:286:3)
        at XMLHttpRequestImpl._dispatch (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\events\EventTarget-impl.js:233:9)
        at fireAnEvent (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\helpers\events.js:18:36)
        at requestErrorSteps (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:131:3)
        at dispatchError (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:60:3)
        at Object.validCORSHeaders (C:\Users\masashi\Desktop\Vue\test\vue-project\node_modules\jsdom\lib\jsdom\living\xhr\xhr-utils.js:75:5)
        at Axios.request (file:///C:/Users/masashi/Desktop/Vue/test/vue-project/node_modules/axios/lib/core/Axios.js:45:41)
        at processTicksAndRejections (node:internal/process/task_queues:95:5) {
      code: 'ERR_NETWORK',
      config: [Object],
      request: XMLHttpRequest {}
    },
    _tag: 'API呼び出し失敗'
  }
}
timestamp=2025-03-20T06:18:54.784Z level=INFO fiber=#10 message="test post"
{ _id: 'Either', _tag: 'Right', right: undefined }
処理成功時の後続処理
timestamp=2025-03-20T06:18:54.793Z level=INFO fiber=#19 message="test post"
{
  _id: 'Either',
  _tag: 'Left',
  left: { error: 'test error', _tag: 'API呼び出し失敗' }
}
API呼び出し失敗のリカバリ処理
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    error: {
      _id: 'ParseError',
      message: '{ readonly name: string; readonly age: number }\n' +
        '└─ ["name"]\n' +
        '   └─ is missing'
    },
    _tag: 'リクエストデータチェックNG'
  }
}
リクエストデータチェックNGのリカバリ処理
```