Skip to content

Add hostname and idn-hostname format validators #82

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

Merged
merged 35 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8d25774
Add hostname format validator
OptimumCode Mar 12, 2024
d3d6b17
Add punycode decoding
OptimumCode Mar 22, 2024
ebc84a2
Copy additional tests for punycode. Return false in case of invalid c…
OptimumCode Mar 22, 2024
02261d9
Add another suppression for detekt on punycode class
OptimumCode Mar 22, 2024
985661d
Draft impl of idn-hostname validator. Add libraries to simplify work …
OptimumCode Apr 4, 2024
901ea8d
Install native dependencies to compile for linuxX64
OptimumCode Apr 5, 2024
c1d3252
Install lib only on linux
OptimumCode Apr 5, 2024
87f870e
Add generator for charcter directionality information
OptimumCode Apr 7, 2024
2b55c64
Cache unicode data
OptimumCode Apr 7, 2024
a1d2706
Add cache to check style
OptimumCode Apr 7, 2024
7804410
Add ktlint task dependency on generated data
OptimumCode Apr 7, 2024
148273d
Add retries for graphql requests
OptimumCode Apr 8, 2024
06ee1a8
Add kdoc for enum constants
OptimumCode Apr 8, 2024
2d5cc4f
Determine bidi rule to apply
OptimumCode Apr 8, 2024
b8dacb7
Add character catagory generation. Add check for specific characters …
OptimumCode Apr 10, 2024
4ab239e
Add generation for derived properties
OptimumCode Apr 10, 2024
496aa5a
Check for disallowed codepoints
OptimumCode Apr 10, 2024
1c41721
Exclude specific cases from disalowed check
OptimumCode Apr 11, 2024
ae1934f
Add tasks dependencies
OptimumCode Apr 11, 2024
a96879c
Ignore detekt checks for generated classes
OptimumCode Apr 11, 2024
f8e05b9
Correct detekt errors. Ignore code points magic numbers
OptimumCode Apr 11, 2024
c086b9f
Check max length before decoding punycode string
OptimumCode Apr 11, 2024
9343fd7
Add punycode encoding for unicode strings to check length rule
OptimumCode Apr 11, 2024
177c9ff
Add internal modifier to generated DerivedProperties enum
OptimumCode Apr 11, 2024
a2a4baf
Extract codepoints validation into separate method
OptimumCode Apr 11, 2024
cd9ad10
Generate joining types enum
OptimumCode Apr 12, 2024
5611f25
Add zero width non joiner rule
OptimumCode Apr 12, 2024
1b45a4f
Clean up build scripts. Move dependnecies to version catalog
OptimumCode Apr 12, 2024
5cc4c80
Add additional tests for idn-hostname format
OptimumCode Apr 12, 2024
3bb0280
Add test for specific cases
OptimumCode Apr 13, 2024
0dcc559
Correct bidi rule implementation
OptimumCode Apr 15, 2024
3ee69e6
Add additional tests for bidi rule violation. Join check for europen …
OptimumCode Apr 15, 2024
b7d1505
Add note about reference impl of idn validation
OptimumCode Apr 15, 2024
d7a263e
Fix detekt error about long line
OptimumCode Apr 15, 2024
0549f41
Update readme
OptimumCode Apr 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
check:
runs-on: ${{ inputs.run-on }}
steps:
- name: 'Install native dependencies'
run: sudo apt-get install -y libunistring-dev
if: runner.os == 'Linux'
- name: 'Checkout Repository'
uses: actions/checkout@v4
with:
Expand All @@ -45,6 +48,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache unicode data
uses: actions/cache@v4
with:
path: build/unicode_dump
key: unicode-dump-${{ hashFiles('build/unicode_dump/*') }}
restore-keys: |
unicode-dump-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/platform-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache unicode data
uses: actions/cache@v4
with:
path: build/unicode_dump
key: unicode-dump-${{ hashFiles('build/unicode_dump/*') }}
restore-keys: |
unicode-dump-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache unicode data
uses: actions/cache@v4
with:
path: build/unicode_dump
key: unicode-dump-${{ hashFiles('build/unicode_dump/*') }}
restore-keys: |
unicode-dump-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache unicode data
uses: actions/cache@v4
with:
path: build/unicode_dump
key: unicode-dump-${{ hashFiles('build/unicode_dump/*') }}
restore-keys: |
unicode-dump-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/snapshot_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ jobs:
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache unicode data
uses: actions/cache@v4
with:
path: build/unicode_dump
key: unicode-dump-${{ hashFiles('build/unicode_dump/*') }}
restore-keys: |
unicode-dump-
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ The library supports `format` assertion. For now only a few formats are supporte
* ipv4
* ipv6
* uuid
* hostname
* idn-hostname

But there is an API to implement the user's defined format validation.
The [FormatValidator](src/commonMain/kotlin/io/github/optimumcode/json/schema/ValidationError.kt) interface can be user for that.
Expand Down Expand Up @@ -389,6 +391,14 @@ You can see the results in the latest workflow execution.
The update to Kotlin 1.9.22 came with an issue for JS incremental compilation.
In case you see an error about main function that already bind please execute `clean` task.

When you build project for **linux** target you might get an error about missing native library.
This is because `de.cketti.unicode:kotlin-codepoints` requires this library to perform string normalization.
This is needed to support `idn-hostname` format. Install this library with the following command:

```bash
sudo apt-get install -y libunistring-dev
```

### Devcontainer

Devcontainers is a cool feature. However, by default in Codespaces and DevPod you will use [VS Code](https://code.visualstudio.com/).
Expand Down
162 changes: 161 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetWithTests
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
import java.util.Locale

plugins {
alias(libs.plugins.kotlin.mutliplatform)
Expand All @@ -22,9 +23,109 @@ repositories {
}

apiValidation {
ignoredProjects += listOf("benchmark", "test-suites")
ignoredProjects += listOf("benchmark", "test-suites", "generator")
}

val generatedSourceDirectory: Provider<Directory> = layout.buildDirectory.dir("generated/source/unicode")

//region Generation tasks block
val generatorConfiguration: Configuration by configurations.creating

dependencies {
generatorConfiguration(project(":generator"))
}

val dumpDir: Provider<Directory> = layout.buildDirectory.dir("unicode_dump")

val dumpCharacterData by tasks.register<JavaExec>("dumpCharacterData") {
onlyIf {
dumpDir.get().asFile.run { !exists() || listFiles().isNullOrEmpty() }
}
outputs.dir(dumpDir)
classpath(generatorConfiguration)
mainClass.set("io.github.optimumcode.unocode.generator.Main")
args(
"dump",
"-o",
dumpDir.get(),
)
}

val generateCharacterDirectionData by tasks.register<JavaExec>("generateCharacterDirectionData") {
inputs.dir(dumpDir)
outputs.dir(generatedSourceDirectory)

dependsOn(dumpCharacterData)

classpath(generatorConfiguration)
mainClass.set("io.github.optimumcode.unocode.generator.Main")
args(
"character-direction",
"-p",
"io.github.optimumcode.json.schema.internal.unicode",
"-o",
generatedSourceDirectory.get(),
"-d",
dumpDir.get(),
)
}

val generateCharacterCategoryData by tasks.register<JavaExec>("generateCharacterCategoryData") {
inputs.dir(dumpDir)
outputs.dir(generatedSourceDirectory)

dependsOn(dumpCharacterData)

classpath(generatorConfiguration)
mainClass.set("io.github.optimumcode.unocode.generator.Main")
args(
"character-category",
"-p",
"io.github.optimumcode.json.schema.internal.unicode",
"-o",
generatedSourceDirectory.get(),
"-d",
dumpDir.get(),
)
}

val generateDerivedProperties by tasks.register<JavaExec>("generateDerivedProperties") {
val dataFile = layout.projectDirectory.dir("generator").dir("data").file("rfc5895_appendix_b_1.txt")
inputs.file(dataFile)
outputs.dir(generatedSourceDirectory)

classpath(generatorConfiguration)
mainClass.set("io.github.optimumcode.unocode.generator.Main")
args(
"derived-properties",
"-p",
"io.github.optimumcode.json.schema.internal.unicode",
"-o",
generatedSourceDirectory.get(),
"-d",
dataFile,
)
}

val generateJoiningTypes by tasks.register<JavaExec>("generateJoiningTypes") {
val dataFile = layout.projectDirectory.dir("generator").dir("data").file("DerivedJoiningType.txt")
inputs.file(dataFile)
outputs.dir(generatedSourceDirectory)

classpath(generatorConfiguration)
mainClass.set("io.github.optimumcode.unocode.generator.Main")
args(
"joining-types",
"-p",
"io.github.optimumcode.json.schema.internal.unicode",
"-o",
generatedSourceDirectory.get(),
"-d",
dataFile,
)
}
//endregion

kotlin {
explicitApi()

Expand Down Expand Up @@ -74,9 +175,18 @@ kotlin {

sourceSets {
commonMain {
kotlin.srcDirs(generatedSourceDirectory)

dependencies {
api(libs.kotlin.serialization.json)
implementation(libs.uri)
// When using approach like above you won't be able to add because block
implementation(libs.kotlin.codepoints.get().toString()) {
because("simplifies work with unicode codepoints")
}
implementation(libs.normalize.get().toString()) {
because("provides normalization required by IDN-hostname format")
}
}
}
commonTest {
Expand All @@ -94,6 +204,19 @@ kotlin {
}
}

targets.configureEach {
val capitalizedTargetName =
name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
tasks.named("compileKotlin$capitalizedTargetName") {
dependsOn(
generateCharacterDirectionData,
generateCharacterCategoryData,
generateDerivedProperties,
generateJoiningTypes,
)
}
}

afterEvaluate {
fun Task.dependsOnTargetTests(targets: List<KotlinTarget>) {
targets.forEach {
Expand Down Expand Up @@ -122,11 +245,48 @@ kotlin {
}
}

afterEvaluate {
val taskNames = setOf("compile", "detekt", "runKtlint")
tasks.configureEach {
// There is something wrong with compileCommonMainKotlinMetadata task
// Gradle cannot find it, but this task uses the generated source directory
// and Gradle reports implicit dependency.
// As a workaround I do this - seems like it is working.
// However, I might be missing something. Need to revisit this later.

if (taskNames.any { name.startsWith(it) }) {
mustRunAfter(
generateCharacterDirectionData,
generateCharacterCategoryData,
generateDerivedProperties,
generateJoiningTypes,
)
}
}
}

koverReport {
filters {
excludes {
packages(
"io.github.optimumcode.json.schema.internal.unicode.*",
"io.github.optimumcode.json.schema.internal.unicode",
)
}
}
}

ktlint {
version.set(libs.versions.ktlint)
reporters {
reporter(ReporterType.HTML)
}
filter {
exclude { el ->
val absolutePath = el.file.absolutePath
absolutePath.contains("generated").and(!el.isDirectory)
}
}
}

afterEvaluate {
Expand Down
52 changes: 52 additions & 0 deletions generator/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import com.expediagroup.graphql.plugin.gradle.config.GraphQLSerializer
import com.expediagroup.graphql.plugin.gradle.graphql
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType

plugins {
// otherwise there is Gradle exception
// https://github.com/gradle/gradle/issues/20084
id(libs.plugins.kotlin.jvm.get().pluginId)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.expediagroup.graphql)

alias(libs.plugins.detekt)
alias(libs.plugins.ktlint)
}

repositories {
mavenCentral()
}

kotlin {
jvmToolchain(11)
}

dependencies {
implementation(libs.kotlinpoet)
implementation(libs.graphql.ktor)
implementation(libs.clikt) {
because("cli for executing generation")
}
}

graphql {
client {
endpoint = "https://www.compart.com/en/unicode/graphql"
packageName = "io.github.optimumcode.unicode.generator.internal.graphql"
serializer = GraphQLSerializer.KOTLINX
}
}

ktlint {
version.set(libs.versions.ktlint)
debug.set(true)
reporters {
reporter(ReporterType.HTML)
}
filter {
exclude { el ->
val absolutePath = el.file.absolutePath
absolutePath.contains("generated").and(!el.isDirectory)
}
}
}
Loading
Loading