Skip to content

Commit

Permalink
chore: merge pull request #2 from thalo-rs/feat/codegen-ts
Browse files Browse the repository at this point in the history
  • Loading branch information
tqwewe committed Jan 28, 2022
2 parents ba2f832 + a195013 commit 3a26edd
Show file tree
Hide file tree
Showing 8 changed files with 594 additions and 278 deletions.
19 changes: 15 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ name = "esdl"
version = "0.2.1"
authors = ["Ari Seyhun <ariseyhun@live.com.au>"]
edition = "2021"
description = "Event-Sourcing Schema Definition Language parser"
description = "Event-sourcing Schema Definition Language parser and code generator"
repository = "https://github.com/thalo-rs/esdl"
license = "MIT OR Apache-2.0"
keywords = ["event-sourcing", "cqrs", "event-driven", "schema"]
categories = ["data-structures", "development-tools", "rust-patterns"]
keywords = ["schema", "esdl", "codegen", "event-sourcing", "cqrs"]
categories = [
"compilers",
"data-structures",
"development-tools::build-utils",
"parsing",
"encoding",
]

[dependencies]
nom = { version = "7.1", features = ["alloc"] }
Expand All @@ -17,4 +23,9 @@ thiserror = "1.0"

[features]
default = []
codegen = []
codegen-rust = []
codegen-typescript = []

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
54 changes: 33 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
# ESDL

**E**vent **S**ourcing Schema **D**efinition **L**anguage
**E**vent-sourcing **S**chema **D**efinition **L**anguage

---

Schema definition language for defining aggregates, commands, events & custom types.

Heavily inspired by GraphQL syntax, you can describe aggregates which can be used for codegen in different languages.

## Code generation

ESDL schemas can be used for code generation.

The [Rust crate](https://crates.io/crates/esdl) currently supports code generation for:

- [Rust](https://docs.rs/esdl/latest/esdl/codegen/rust/struct.RustCompiler.html)
- [TypeScript](https://docs.rs/esdl/latest/esdl/codegen/typescript/struct.TypeScriptCompiler.html)

Additional languages may be added in the future. Contributions are welcome!

## Example

```
Expand Down Expand Up @@ -40,39 +51,40 @@ type User {

### Scalar Types

| Scalar | Rust Type |
| ----------- | ---------------------------------------------------------------------------------------------------- |
| `String` | [`String`](https://doc.rust-lang.org/stable/std/string/struct.String.html) |
| `Int` | [`i64`](https://doc.rust-lang.org/stable/std/primitive.i64.html) |
| `Float` | [`f64`](https://doc.rust-lang.org/stable/std/primitive.f64.html) |
| `Bool` | [`bool`](https://doc.rust-lang.org/stable/std/primitive.bool.html) |
| `Timestamp` | [`chrono::DateTime<chrono::FixedOffset>`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) |
| Scalar | Rust Type | TypeScript Type |
| ----------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `String` | [`String`](https://doc.rust-lang.org/stable/std/string/struct.String.html) | [`string`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean) |
| `Int` | [`i64`](https://doc.rust-lang.org/stable/std/primitive.i64.html) | [`number`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean) |
| `Float` | [`f64`](https://doc.rust-lang.org/stable/std/primitive.f64.html) | [`number`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean) |
| `Bool` | [`bool`](https://doc.rust-lang.org/stable/std/primitive.bool.html) | [`boolean`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean) |
| `Timestamp` | [`DateTime<FixedOffset>`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) | [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) |

### Optional & Required

Types can be marked as required by adding the `!` suffix.

| Type | Syntax | Example |
| -------- | -------- | --------- |
| Optional | _(none)_ | `String` |
| Required | `!` | `String!` |
| Type | Syntax | Example |
| -------- | ------ | --------- |
| Optional | `T` | `String` |
| Required | `T!` | `String!` |

### Repeating Types

Types can be repeated by wrapping them in `[]`.

| Type | Syntax | Example |
| ------ | --------- | ---------- |
| Single | _(none)_ | `String` |
| Array | `[`...`]` | `[String]` |
| Type | Syntax | Example |
| ------ | ------ | ---------- |
| Single | `T` | `String` |
| Array | `[T]` | `[String]` |

Remember, we can mark types as required, even in arrays.

| Type | Syntax | Example |
| -------------------- | ---------- | ----------- |
| Optional Array | `[`...`]` | `[String]` |
| Required Array | `[`...`]!` | `[String]!` |
| Required Array Items | `[`...`!]` | `[String!]` |
| Type | Syntax | Example |
| -------------------- | ------- | ------------ |
| Optional Array | `[T]` | `[String]` |
| Required Array | `[T]!` | `[String]!` |
| Required Array Items | `[T!]` | `[String!]` |
| Required Array Items | `[T!]!` | `[String!]!` |

---

Expand Down
122 changes: 122 additions & 0 deletions src/codegen/compiler/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#![allow(unused_must_use)]

use std::{collections::HashMap, env, fs, path::Path};

use crate::schema::{Command, CustomType, Event, Schema};

use super::error::Error;

#[cfg(feature = "codegen-rust")]
pub mod rust;
#[cfg(feature = "codegen-typescript")]
pub mod typescript;

/// Compile schemas into Rust code.
///
/// # Example
///
/// ```
/// use esdl::codegen::{rust::RustCompiler, Compiler};
///
/// Compiler::new(RustCompiler)
/// .add_schema_file("./bank-account.esdl")?
/// .compile()?;
/// ```
#[derive(Default)]
pub struct Compiler<C> {
compiler: C,
schemas: Vec<Schema>,
}

impl<C: Compile> Compiler<C> {
/// Creates a new compiler instance.
pub fn new(compiler: C) -> Self {
Compiler {
compiler,
schemas: Vec::new(),
}
}

/// Adds a schema.
pub fn add_schema(mut self, schema: Schema) -> Self {
self.schemas.push(schema);
self
}

/// Add a schema from a yaml file.
pub fn add_schema_file<P: AsRef<Path>>(self, path: P) -> Result<Self, Error> {
let content = fs::read_to_string(path)?;
self.add_schema_str(&content)
}

/// Add a schema from yaml string.
pub fn add_schema_str(self, content: &str) -> Result<Self, Error> {
let parsed_schema = crate::parse(content).map_err(|err| Error::Parse(err.to_string()))?;
let schema = Schema::validate_parsed_schema(parsed_schema)?;
Ok(self.add_schema(schema))
}

/// Compile schemas into Rust code and save in OUT_DIR.
pub fn compile(self) -> Result<(), Error> {
let out_dir = env::var("OUT_DIR").unwrap();

let compiler = &self.compiler;
for schema in self.schemas {
let code = compile_schema(compiler, &schema);
fs::write(format!("{}/{}.rs", out_dir, schema.aggregate.name), code)?;
}

Ok(())
}

/// Compile schemas into Rust code and outputs as hashmap.
///
/// The resulting hashmap key is the aggregate name, and the value is the generated code.
pub fn compile_to_strings(self) -> Result<HashMap<String, String>, Error> {
let mut codes = HashMap::with_capacity(self.schemas.len());

let compiler = &self.compiler;
for schema in self.schemas {
let code = compile_schema(compiler, &schema);
codes.insert(schema.aggregate.name, code);
}

Ok(codes)
}
}

pub fn compile_schema<C: Compile>(compiler: &C, schema: &Schema) -> String {
let mut code = String::new();

compiler.compile_before(&mut code);

compiler.compile_schema_types(&mut code, &schema.types);

compiler.compile_schema_events(&mut code, &schema.aggregate.name, &schema.events);

compiler.compile_schema_commands(
&mut code,
&schema.aggregate.name,
&schema.aggregate.commands,
);

compiler.compile_after(&mut code);

code
}

pub trait Compile {
fn compile_before(&self, _code: &mut String) {}
fn compile_after(&self, _code: &mut String) {}

fn compile_schema_types(&self, code: &mut String, types: &HashMap<String, CustomType>);

fn compile_schema_events(&self, code: &mut String, name: &str, events: &HashMap<String, Event>);

fn compile_schema_commands(
&self,
code: &mut String,
name: &str,
commands: &HashMap<String, Command>,
);
}
Loading

0 comments on commit 3a26edd

Please sign in to comment.