Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ generated-*/
# Temporary test directories for parallel test execution
target/test-regular/
target/test-wrapped/
target/test-graphql/

src/test/resources/internal

Expand Down
76 changes: 76 additions & 0 deletions .mdl/defs/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -1837,12 +1837,88 @@ popd
ret success:bool=true
```

# action: test-gen-graphql

Generate GraphQL SDL schemas and validate them.

```bash
BABOON_BIN="${action.build.binary}"
TEST_DIR="./target/test-graphql"

rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"

$BABOON_BIN \
--model-dir ./baboon-compiler/src/test/resources/baboon/ \
:graphql \
--output "$TEST_DIR" \
--disable-conversions=true \
--runtime=without

ret success:bool=true
ret test_dir:string="$TEST_DIR"
```

# action: test-graphql

Validate generated GraphQL schemas using graphql-js buildSchema.

```bash
TEST_DIR="${action.test-gen-graphql.test_dir}"

pushd ./test/gql-stub
npm install
node validate.mjs "../../$TEST_DIR"
popd

ret success:bool=true
```

# action: test-gen-openapi

Generate OpenAPI 3.1 component schemas and validate them.

```bash
BABOON_BIN="${action.build.binary}"
TEST_DIR="./target/test-openapi"

rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"

$BABOON_BIN \
--model-dir ./baboon-compiler/src/test/resources/baboon/ \
:openapi \
--output "$TEST_DIR" \
--disable-conversions=true \
--runtime=without

ret success:bool=true
ret test_dir:string="$TEST_DIR"
```

# action: test-openapi

Validate generated OpenAPI schemas using swagger-parser.

```bash
TEST_DIR="${action.test-gen-openapi.test_dir}"

pushd ./test/oas-stub
npm install
node validate.mjs "../../$TEST_DIR"
popd

ret success:bool=true
```

# action: test

Run complete test suite (orchestrator action).

```bash
dep action.test-sbt-basic
dep action.test-graphql
dep action.test-openapi
dep action.test-cs-regular
dep action.test-scala-regular
dep action.test-python-regular
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

Baboon is a Domain Modeling Language (DML) compiler with schema evolution support. It compiles `.baboon` domain model files to multiple target languages (Scala, C#, Python, Rust, TypeScript, Kotlin, Java, Dart, Swift) with automatic JSON and UEBA codec generation.
Baboon is a Domain Modeling Language (DML) compiler with schema evolution support. It compiles `.baboon` domain model files to multiple target languages (Scala, C#, Python, Rust, TypeScript, Kotlin, Java, Dart, Swift) with automatic JSON and UEBA codec generation. It also supports schema-only output formats (GraphQL SDL, OpenAPI 3.1).

## Essential Commands

Expand Down Expand Up @@ -108,6 +108,8 @@ baboon \
- `java/` - Java code generation with Jackson JSON codecs and UEBA binary codecs
- `dart/` - Dart code generation with dart:convert JSON codecs and UEBA binary codecs
- `swift/` - Swift code generation with JSONSerialization JSON codecs and UEBA binary codecs
- `graphql/` - GraphQL SDL schema generation (type definitions only, no codecs)
- `openapi/` - OpenAPI 3.1 JSON Schema generation (component schemas only, no codecs)
- Each generator produces source files, codec implementations and conversions from lower versions to higher ones

5. **Runtime Support (`src/main/resources/baboon-runtime/`)**
Expand Down Expand Up @@ -146,7 +148,7 @@ Baboon files support:

- Unit tests for individual components
- Integration tests with full compilation cycles
- Generated code tests in `test/cs-stub/`, `test/sc-stub/`, `test/py-stub/`, `test/rs-stub/`, `test/ts-stub/`, `test/kt-stub/`, `test/jv-stub/`, `test/dt-stub/`, and `test/sw-stub/`
- Generated code tests in `test/cs-stub/`, `test/sc-stub/`, `test/py-stub/`, `test/rs-stub/`, `test/ts-stub/`, `test/kt-stub/`, `test/jv-stub/`, `test/dt-stub/`, `test/sw-stub/`, `test/gql-stub/`, and `test/oas-stub/`
- Cross-platform compatibility tests in `test/conv-test-{cs,sc,py,rs,ts,kt,jv,dt,sw}/` (verifies JSON/UEBA interop across all languages)
- Evolution tests validating schema migration

Expand Down
151 changes: 100 additions & 51 deletions baboon-compiler/.js/src/main/scala/io/septimalmind/baboon/BaboonJS.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ object BaboonJS {
obj.asInstanceOf[JSCompilationError]
}

class BaboonCompilationException(val issues: NEList[BaboonIssue])
extends RuntimeException(s"Compilation failed with ${issues.toList.size} issues")
class BaboonCompilationException(val issues: NEList[BaboonIssue]) extends RuntimeException(s"Compilation failed with ${issues.toList.size} issues")

/**
* Compilation result
Expand Down Expand Up @@ -330,7 +329,7 @@ object BaboonJS {
private def createOutputOptions(generic: js.UndefOr[JSGenericOptions]): OutputOptionsJS = {
val g = generic.getOrElse(js.Dynamic.literal().asInstanceOf[JSGenericOptions])
OutputOptionsJS(
safeToRemoveExtensions = Set("meta", "cs", "json", "scala", "kt", "py", "rs", "ts", "java", "dart", "swift"),
safeToRemoveExtensions = Set("meta", "cs", "json", "scala", "kt", "py", "rs", "ts", "java", "dart", "swift", "graphql"),
runtime = parseRuntimeOpt(g.runtime),
generateConversions = !g.disableConversions.getOrElse(false),
generateTests = g.generateTests.getOrElse(false),
Expand Down Expand Up @@ -535,6 +534,20 @@ object BaboonJS {
pragmas = Map.empty,
),
)
case "graphql" =>
CompilerTargetJS.GqlTarget(
id = "GraphQL",
output = createOutputOptions(target.generic),
generic = createGenericOptions(target.generic),
language = GqlOptions(pragmas = Map.empty),
)
case "openapi" =>
CompilerTargetJS.OasTarget(
id = "OpenAPI",
output = createOutputOptions(target.generic),
generic = createGenericOptions(target.generic),
language = OasOptions(pragmas = Map.empty),
)
case other => throw new IllegalArgumentException(s"Unknown target language: $other")
}
}
Expand Down Expand Up @@ -652,17 +665,37 @@ object BaboonJS {
)
}

private def toGqlTarget(target: CompilerTargetJS.GqlTarget): CompilerTarget.GqlTarget = {
CompilerTarget.GqlTarget(
id = target.id,
output = toOutputOptions(target.output),
generic = target.generic,
language = target.language,
)
}

private def toOasTarget(target: CompilerTargetJS.OasTarget): CompilerTarget.OasTarget = {
CompilerTarget.OasTarget(
id = target.id,
output = toOutputOptions(target.output),
generic = target.generic,
language = target.language,
)
}

private def toCompilerTargets(targets: Seq[CompilerTargetJS]): Seq[CompilerTarget] = {
targets.map {
case t: CompilerTargetJS.CSTarget => toCSTarget(t)
case t: CompilerTargetJS.ScTarget => toScTarget(t)
case t: CompilerTargetJS.PyTarget => toPyTarget(t)
case t: CompilerTargetJS.RsTarget => toRsTarget(t)
case t: CompilerTargetJS.TsTarget => toTsTarget(t)
case t: CompilerTargetJS.KtTarget => toKtTarget(t)
case t: CompilerTargetJS.JvTarget => toJvTarget(t)
case t: CompilerTargetJS.DtTarget => toDtTarget(t)
case t: CompilerTargetJS.SwTarget => toSwTarget(t)
case t: CompilerTargetJS.CSTarget => toCSTarget(t)
case t: CompilerTargetJS.ScTarget => toScTarget(t)
case t: CompilerTargetJS.PyTarget => toPyTarget(t)
case t: CompilerTargetJS.RsTarget => toRsTarget(t)
case t: CompilerTargetJS.TsTarget => toTsTarget(t)
case t: CompilerTargetJS.KtTarget => toKtTarget(t)
case t: CompilerTargetJS.JvTarget => toJvTarget(t)
case t: CompilerTargetJS.DtTarget => toDtTarget(t)
case t: CompilerTargetJS.SwTarget => toSwTarget(t)
case t: CompilerTargetJS.GqlTarget => toGqlTarget(t)
case t: CompilerTargetJS.OasTarget => toOasTarget(t)
}
}

Expand Down Expand Up @@ -740,25 +773,29 @@ object BaboonJS {
}
// skip ADT branch members (they're owned by their parent ADT)
if (!user.ownedByAdt) {
result.push(JSTypeInfo(
pkg = pkg.toString,
version = version.toString,
id = typeId.toString,
name = user.defn.id.name.name,
kind = kind,
))
result.push(
JSTypeInfo(
pkg = pkg.toString,
version = version.toString,
id = typeId.toString,
name = user.defn.id.name.name,
kind = kind,
)
)
}
case _ => // skip builtins
}
}
for (alias <- domain.aliases) {
result.push(JSTypeInfo(
pkg = pkg.toString,
version = version.toString,
id = s"type:${alias.name.name}",
name = alias.name.name,
kind = "alias",
))
result.push(
JSTypeInfo(
pkg = pkg.toString,
version = version.toString,
id = s"type:${alias.name.name}",
name = alias.name.name,
kind = "alias",
)
)
}
}
}
Expand All @@ -777,15 +814,13 @@ object BaboonJS {
typeId: String,
): JSGenerateResult = {
try {
val family = model.asInstanceOf[BaboonLoadedModelImpl].family
val parsedPkg = parsePkg(pkg)
val parsedVer = Version.parse(version)
val enquiries = new BaboonEnquiries.BaboonEnquiriesImpl
val family = model.asInstanceOf[BaboonLoadedModelImpl].family
val parsedPkg = parsePkg(pkg)
val parsedVer = Version.parse(version)
val enquiries = new BaboonEnquiries.BaboonEnquiriesImpl

val lineage = family.domains.toMap.getOrElse(parsedPkg,
throw new RuntimeException(s"Package not found: $pkg"))
val domain = lineage.versions.toMap.getOrElse(parsedVer,
throw new RuntimeException(s"Version not found: $version"))
val lineage = family.domains.toMap.getOrElse(parsedPkg, throw new RuntimeException(s"Package not found: $pkg"))
val domain = lineage.versions.toMap.getOrElse(parsedVer, throw new RuntimeException(s"Version not found: $version"))

val member = domain.defs.meta.nodes.collectFirst {
case (tid, m: DomainMember.User) if tid.toString == typeId => m
Expand Down Expand Up @@ -842,10 +877,16 @@ object BaboonJS {
case e: BaboonCompilationException =>
JSCompilationResult.failure(issuesToStructuredErrors(e.issues))
case e: Throwable =>
JSCompilationResult.failure(js.Array(createJSCompilationError(
s"Compilation failed: ${e.getMessage}\n${e.getStackTrace.mkString("\n")}",
None, None, None,
)))
JSCompilationResult.failure(
js.Array(
createJSCompilationError(
s"Compilation failed: ${e.getMessage}\n${e.getStackTrace.mkString("\n")}",
None,
None,
None,
)
)
)
}.toJSPromise
} catch {
case e: BaboonCompilationException =>
Expand All @@ -855,10 +896,16 @@ object BaboonJS {
case e: Throwable =>
Future
.successful(
JSCompilationResult.failure(js.Array(createJSCompilationError(
s"Compilation failed: ${e.getMessage}\n${e.getStackTrace.mkString("\n")}",
None, None, None,
)))
JSCompilationResult.failure(
js.Array(
createJSCompilationError(
s"Compilation failed: ${e.getMessage}\n${e.getStackTrace.mkString("\n")}",
None,
None,
None,
)
)
)
).toJSPromise
}
}
Expand Down Expand Up @@ -925,15 +972,17 @@ object BaboonJS {
m: DefaultModule[F[Throwable, _]],
): F[Throwable, Seq[OutputFileWithPath]] = {
val module = target match {
case t: CompilerTargetJS.CSTarget => new BaboonJsCSModule[F](toCSTarget(t))
case t: CompilerTargetJS.ScTarget => new BaboonJsScModule[F](toScTarget(t))
case t: CompilerTargetJS.PyTarget => new BaboonJsPyModule[F](toPyTarget(t))
case t: CompilerTargetJS.RsTarget => new BaboonJsRsModule[F](toRsTarget(t))
case t: CompilerTargetJS.TsTarget => new BaboonJsTsModule[F](toTsTarget(t))
case t: CompilerTargetJS.KtTarget => new BaboonJsKtModule[F](toKtTarget(t))
case t: CompilerTargetJS.JvTarget => new BaboonJsJvModule[F](toJvTarget(t))
case t: CompilerTargetJS.DtTarget => new BaboonJsDtModule[F](toDtTarget(t))
case t: CompilerTargetJS.SwTarget => new BaboonJsSwModule[F](toSwTarget(t))
case t: CompilerTargetJS.CSTarget => new BaboonJsCSModule[F](toCSTarget(t))
case t: CompilerTargetJS.ScTarget => new BaboonJsScModule[F](toScTarget(t))
case t: CompilerTargetJS.PyTarget => new BaboonJsPyModule[F](toPyTarget(t))
case t: CompilerTargetJS.RsTarget => new BaboonJsRsModule[F](toRsTarget(t))
case t: CompilerTargetJS.TsTarget => new BaboonJsTsModule[F](toTsTarget(t))
case t: CompilerTargetJS.KtTarget => new BaboonJsKtModule[F](toKtTarget(t))
case t: CompilerTargetJS.JvTarget => new BaboonJsJvModule[F](toJvTarget(t))
case t: CompilerTargetJS.DtTarget => new BaboonJsDtModule[F](toDtTarget(t))
case t: CompilerTargetJS.SwTarget => new BaboonJsSwModule[F](toSwTarget(t))
case t: CompilerTargetJS.GqlTarget => new BaboonJsGqlModule[F](toGqlTarget(t))
case t: CompilerTargetJS.OasTarget => new BaboonJsOasModule[F](toOasTarget(t))
}

Injector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ class BaboonJsSwModule[F[+_, +_]: Error2: TagKK](compilerTarget: CompilerTarget.
make[CompilerTarget.SwTarget].fromValue(compilerTarget)
}

class BaboonJsGqlModule[F[+_, +_]: Error2: TagKK](compilerTarget: CompilerTarget.GqlTarget) extends ModuleDef {
include(new BaboonCommonGqlModule[F]())
make[CompilerTarget.GqlTarget].fromValue(compilerTarget)
}

class BaboonJsOasModule[F[+_, +_]: Error2: TagKK](compilerTarget: CompilerTarget.OasTarget) extends ModuleDef {
include(new BaboonCommonOasModule[F]())
make[CompilerTarget.OasTarget].fromValue(compilerTarget)
}

class BaboonCodecModuleJS[F[+_, +_]: Error2: MaybeSuspend2: TagKK](
parOps: ParallelErrorAccumulatingOps2[F]
) extends ModuleDef {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ object CompilerTargetJS {
generic: GenericOptions,
language: SwOptions,
) extends CompilerTargetJS

case class GqlTarget(
id: String,
output: OutputOptionsJS,
generic: GenericOptions,
language: GqlOptions,
) extends CompilerTargetJS

case class OasTarget(
id: String,
output: OutputOptionsJS,
generic: GenericOptions,
language: OasOptions,
) extends CompilerTargetJS
}

final case class OutputOptionsJS(
Expand Down
Loading
Loading