もっと型で表現する

if (startDate.isAfter(endDate)) {
    throw IllegalStateException()
}

contractRepository.save(startDate, endDate)

よりも、

val contractPeriod = ContractPeriod(startDate, endDate)
contractRepository.save(contractPeriod)

data class ContractPeriod(
    val startDate: LocalDate,
    val endDate: LocalDate,
) {
    init {
        if (startDate.isAfter(endDate)) {
            // ここは専用の例外でもいい
            throw IllegalStateException()
        }
    }
}

さらに、例えば契約の履歴をコレクションにまとめることで、契約期間の整合性をとることもひとつの専用の型で表現できる。

data class ContractHistory(
    val history: List<ContractPeriod>,
) {
    fun addContract(contract: ContractPeriod): ContractHistory {
        if (history.isEmpty()) {
            return ContractHistory(listOf(contract))
        }

        if (!validateContractPeriod(contract)) {
            throw IllegalStateException()
        }

        return ContractHistory(history + contract)
    }

    private fun validateContractPeriod(newContract: ContractPeriod): Boolean {
        if (history.isEmpty()) return true
        val lastContract = history.last()
        // ここらへんは、period.isOverlap(otherPeriod)とかしたほうが良いよいかもしれない
        return !lastContract.endDate.isBefore(newContract.startDate)
    }
}

マイドライブにはアクセスできるが、共有ドライブでは404になってしまう事象

事象

ドライブAPIを実行すると以下のようなエラーレスポンスが帰ってくる。

{
  "code": 404,
  "errors": [
    {
      "domain": "global",
      "location": "fileId",
      "locationType": "parameter",
      "message": "File not found: 1Ad-2kOYlMd1JvNPR0o4lAMLk07Pid4S6.",
      "reason": "notFound"
    }
  ],
  "message": "File not found: 1Ad-2kOYlMd1JvNPR0o4lAMLk07Pid4S6."
}

developers.google.com

対応

共有ドライブに対してはデフォルトではAPIからのアクセスができない(?)ようなので、ファイルを作成する際に適切なパラメータ設定が必要。

    override fun upload(
        localPath: String,
        folderId: String?,
        fileName: String?,
    ): String {
        val fileMetadata =
            DriveFile().apply {
                name = fileName ?: Paths.get(localPath).fileName.toString()
                folderId?.let { parents = listOf(it) }
            }

        val csvFile = Paths.get(localPath).toFile()
        val mediaContent = FileContent("text/csv", csvFile)

        val uploadedFile =
            drive
                .files()
                .create(fileMetadata, mediaContent)
                .setFields("id")
                .setSupportsAllDrives(true) // これを追加する
                .execute()

        return uploadedFile.id
    }

参考

共有ドライブの管理  |  Google Drive  |  Google for Developers

共有ドライブのサポートを実装する  |  Google Drive  |  Google for Developers

OpenAI APIで429(too many request)が出た時

OpenAIのAPIエラーについては以下にまとまっています。
https://platform.openai.com/docs/guides/error-codes/api-errors

429の項にあるように、

Cause: You have run out of credits or hit your maximum monthly spend. Solution: Buy more credits or learn how to increase your limits.

を試せば良いです。 私は個人開発をしている時に発生したので、単純にクレジットを増やすことで解決しました。
https://platform.openai.com/settings/organization/billing/overview
上記のページから[Add to credit balance]をクリックして金額を増やせばOK。

もちろん、純粋なレートリミットの場合もありうるので、レートリミットのページを参考に必要があればTierを上げるなどの対処が必要です。

removeAllEmoji()をした際に謎にテストが失敗した件

emoji-javaというライブラリを使用して絵文字を削除するコードを書いたところ、テストが失敗した。
人の目からすれば期待値と実際の値は同じように見えているため不可解。
AI先生に聞いてみたところ、不可視文字というものが紛れている場合があるようなので、それも削除する必要があるみたい。

fun String.removeEmojis: String {
    return EmojiParser.removeAllEmojis(this)
        .replace(Regex("[\\uFE0F\\u200B-\\u200D]"), "") // FE0FとZWJ/ZWNJも削除
        .trim()
}

java.nio.file.NoSuchFileException: /Users/XXXXXXX/.gradle/caches/transforms-3/4343cfb6515206cd75593cbe4fe4751c/results.bin

事象

./gradlew bootrunをしたところ、タイトルのエラーが発生。原因不明。

対処

rm -rf ~/.gradle

なお、Gradleのユーザーホームディレクトリを削除するとcachesをはじめとするファイルも全て削除される。 そのため、再度./gradlew bootrunを実行すると依存ライブラリを初め諸々のダウンロードが再実行されるため、少し時間がかかる。

参考

java - Android Gradle build fails from cached files - Stack Overflow

Gradle Directories

VagrantとVirtualbox

コンテナセキュリティと言う本を読んでいて、仮想環境を構築するのに筆者がVagrantVirtualboxを使用しているようなので、自分でも同じ環境を作る。

Virtualbox

公式のインストールページから

Vagrant

こっちはコマンドラインから。

Install | Vagrant | HashiCorp Developer

brew tap hashicorp/tap
brew install hashicorp/tap/hashicorp-vagrant
# 私は~/devというディレクトリに開発関連の資材を全て保存しているのでそこで行う
# 一応vagrantディレクトリで作業する
mkdir vagrant
cd vagrant
# 作成する仮想環境ごとにディレクトリを作成する(Vagrantfileが作成されるので、ディレクトリは分ける必要がある)
mkdir ubuntu
cd ubuntu
vagrant init ubuntu/jammy64
# 起動
vagrant up

上記まで行うと、Virtulabox上(GUI)で仮想環境が起動されたことがわかる。

起動が確認できたら、以下のコマンドでssh接続する

vagrant ssh

gcloud buildsは.gitignoreに記載されているファイルをビルドに含めない

問題

https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore

以下引用。

The default content of the generated .gcloudignore file, which can be overridden with --ignore-file, is as follows:

.gcloudignore .git .gitignore

というわけで、.gitignoreに書かれているファイルは、gcloud buildsの対象外になる。

とはいえ、build/libs/xxxx.jarなどはgitで管理する必要はないので.gitignoreに含めるのは仕方がない。一方で、アプリを起動するためには最終的にビルドした.jarファイルが必要にはなる。 つまり、.gitignoreには書きつつもgcloud buildsコマンドを実行した際にビルドの対象とする必要がある。

対策

.gcloudignoreファイルを作成する。

以下のファイルを作成する。こうすることで、アップロードしたいファイルのみを指定することができる。

# ignore all
*

# upload files
!api/**
!Dockerfile