From fc300d82c1f14ad44b1a1e686c4d7e4b7276848e Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Mon, 20 Sep 2021 16:03:56 -0400 Subject: [PATCH 1/3] added safeguard for providers that panic --- templates/rust/provider-integration.hbs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/templates/rust/provider-integration.hbs b/templates/rust/provider-integration.hbs index a8b25ba..4024d68 100644 --- a/templates/rust/provider-integration.hbs +++ b/templates/rust/provider-integration.hbs @@ -73,10 +73,17 @@ pub (crate) mod {{snakeCase file.unhyphenated}} { ) -> Result> { let inputs = populate_inputs(data).map_err(|e| NativeComponentError::new(format!("Input deserialization error: {}", e.to_string())))?; let (outputs, stream) = get_outputs(); - let result = crate::components::{{snakeCase namespace.name.value}}::job(inputs, outputs, context).await; + let result = tokio::spawn(crate::components::{{snakeCase namespace.name.value}}::job(inputs, outputs, context)) + .await + .map_err(|e| { + Box::new(NativeComponentError::new(format!( + "Component panicked: {}", + e + ))) + })?; match result { Ok(_) => Ok(stream), - Err(e) => Err(Box::new(NativeComponentError::new(format!("Job failed: {}", e.to_string())))), + Err(e) => Err(Box::new(NativeComponentError::new(e.to_string()))), } } } From 29f85d0b2f7bff772e5492f59ee2c77d1906ad63 Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Wed, 22 Sep 2021 14:53:51 -0400 Subject: [PATCH 2/3] added From impl for TransportMap, updated job result to type alias JobResult --- templates/rust/partials/common/native-inputs.hbs | 10 ++++++++++ templates/rust/partials/wapc-integration/Outputs.hbs | 2 +- templates/rust/provider-component.hbs | 5 ++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/templates/rust/partials/common/native-inputs.hbs b/templates/rust/partials/common/native-inputs.hbs index dd32b83..eba6bbe 100644 --- a/templates/rust/partials/common/native-inputs.hbs +++ b/templates/rust/partials/common/native-inputs.hbs @@ -15,6 +15,16 @@ pub struct Inputs { {{/each}} } +impl From for TransportMap { + fn from(inputs: Inputs) -> TransportMap { + let mut map = TransportMap::new(); + {{#each fields }} + map.insert("{{snakeCase name.value}}".to_owned(), MessageTransport::success(&inputs.{{snakeCase name.value}})); + {{/each}} + map + } +} + static INPUTS_LIST: &[(&str, &str)] = &[{{#join fields ","}}("{{name.value}}", "{{codegen-type type}}"){{/join}}]; #[must_use] diff --git a/templates/rust/partials/wapc-integration/Outputs.hbs b/templates/rust/partials/wapc-integration/Outputs.hbs index 9522c82..bcd7460 100644 --- a/templates/rust/partials/wapc-integration/Outputs.hbs +++ b/templates/rust/partials/wapc-integration/Outputs.hbs @@ -10,7 +10,7 @@ fn get_outputs() -> Outputs { #[derive(Debug, PartialEq, Clone)] pub struct GuestPort{{pascalCase name.value}} {} -impl PortSender for GuestPortOutput { +impl PortSender for GuestPort{{pascalCase name.value}} { type PayloadType = {{> expand-type type}}; fn get_name(&self) -> String { "{{name.value}}".to_string() diff --git a/templates/rust/provider-component.hbs b/templates/rust/provider-component.hbs index 329d77b..16fede8 100644 --- a/templates/rust/provider-component.hbs +++ b/templates/rust/provider-component.hbs @@ -1,11 +1,10 @@ -use vino_provider::native::prelude::*; use crate::generated::{{snakeCase schema.unhyphenated}}::*; pub(crate) async fn job( input: Inputs, output: Outputs, - _context: crate::Context, -) -> Result<(), Box> { + context: crate::Context, +) -> JobResult { Ok(()) } From b3a8faa197a3e9a575cdd4f714e24c266ad7f4bd Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Thu, 28 Oct 2021 14:27:26 -0400 Subject: [PATCH 3/3] updated codegen for API changes --- src/common.ts | 7 +- src/languages/json/json.ts | 167 +++++++++++++----- .../rust/provider-component-module.ts | 1 - src/types.ts | 91 ++++++++++ templates/rust/interface.hbs | 10 +- .../rust/partials/common/expand-type.hbs | 2 + .../rust/partials/common/native-inputs.hbs | 30 +++- .../rust/partials/common/native-outputs.hbs | 67 ++++++- .../rust/partials/common/type-signature.hbs | 45 +++++ .../rust/partials/wapc-integration/Inputs.hbs | 25 ++- .../partials/wapc-integration/Outputs.hbs | 46 +++-- .../wapc-integration/TypeDefinition.hbs | 2 +- .../wellknown-integration/component.hbs | 2 +- templates/rust/provider-component.hbs | 2 +- templates/rust/provider-integration.hbs | 29 ++- templates/rust/wapc-component.hbs | 2 +- templates/rust/wapc-integration.hbs | 12 +- templates/rust/wapc-lib.hbs | 6 +- templates/rust/wellknown-integration.hbs | 19 +- test/fixtures/hello-world.widl | 3 +- test/fixtures/http.widl | 12 ++ test/fixtures/include/types.widl | 10 ++ test/json.test.ts | 72 ++++++++ 23 files changed, 545 insertions(+), 117 deletions(-) create mode 100644 src/types.ts create mode 100644 templates/rust/partials/common/type-signature.hbs create mode 100644 test/fixtures/http.widl create mode 100644 test/fixtures/include/types.widl create mode 100644 test/json.test.ts diff --git a/src/common.ts b/src/common.ts index 5356b74..de01a91 100644 --- a/src/common.ts +++ b/src/common.ts @@ -3,7 +3,6 @@ import path from 'path'; import findroot from 'find-root'; import DEBUG from 'debug'; import { handlebars } from 'widl-template'; -import { ast } from '@wapc/widl'; import { AbstractNode, Kind, ListType, MapType, Named, Optional } from '@wapc/widl/ast'; import yargs from 'yargs'; export const debug = DEBUG('vino-codegen'); @@ -47,8 +46,8 @@ export const DEFAULT_CODEGEN_TYPE = CODEGEN_TYPE.WapcIntegration; export function readFile(path: string): string { try { return fs.readFileSync(path, 'utf-8'); - } catch (e) { - throw new Error(`Could not read file at ${path}: ${e.message}`); + } catch (e: unknown) { + throw new Error(`Could not read file at ${path}: ${e}`); } } @@ -210,7 +209,7 @@ export function commitOutput(src: string, path?: string, options: CommitOptions debug(`Refusing to overwrite ${path}`); if (options.silent) return; else { - console.error(`${path} exists, to overwrite pass --force to the codegen or delete the file`); + debug(`${path} exists, to overwrite pass --force to the codegen or delete the file`); return; } } diff --git a/src/languages/json/json.ts b/src/languages/json/json.ts index ee93dc6..78b4076 100644 --- a/src/languages/json/json.ts +++ b/src/languages/json/json.ts @@ -5,7 +5,6 @@ import { registerTypePartials, JSON_TYPE, readFile, - codegen, outputOpts, widlOpts, CommonOutputOptions, @@ -19,19 +18,25 @@ import { Annotation, Definition, Document, - FieldDefinition, - ImportDefinition, - InterfaceDefinition, Kind, ListType, MapType, Named, NamespaceDefinition, - StringValue, + Optional, + Type, TypeDefinition, } from '@wapc/widl/ast'; import { registerHelpers } from 'widl-template'; +import { + ComponentSignature, + isWidlType, + ProviderSignature, + StructSignature, + TypeMap, + TypeSignature, +} from '../../types'; const LANG = LANGUAGE.JSON; const TYPE = JSON_TYPE.Interface; @@ -64,17 +69,6 @@ function isType(def: Definition): def is TypeDefinition { return def.isKind(Kind.TypeDefinition); } -interface Component { - name: string; - inputs: Port[]; - outputs: Port[]; -} - -interface Port { - name: string; - type_string: string; -} - interface HasName extends AbstractNode { name: { value: string }; } @@ -83,28 +77,76 @@ function findByName(defs: T[], name: string): T | undefined { return defs.find(def => def.name.value === name); } -function distillType(types: TypeDefinition[], node: TypeDefinition | MapType | ListType | Named): any { - if (node.isKind(Kind.Named)) { - const name = (node).name.value; - const reference = findByName(types, name); - if (!reference) return codegen(node as Named); - switch (reference.kind) { - case Kind.TypeDefinition: - return Object.fromEntries( - (reference).fields.map(field => [ - field.name.value, - distillType(types, field.type as Named | MapType | ListType), - ]), - ); - default: - return codegen(reference); +function reduceTypeDefinition(def: TypeDefinition): StructSignature { + const fields: Record = {}; + for (const field of def.fields) { + fields[field.name.value] = reduceType(field.type, field.annotations); + } + + return { + name: def.name.value, + fields, + }; +} + +function getAnnotation(name: string, annotations: Annotation[]): Annotation | undefined { + const result = annotations.filter(a => a.name.value == name)[0]; + return result; +} + +function reduceType(type: Type, annotations: Annotation[] = []): TypeSignature { + switch (type.getKind()) { + case Kind.Named: { + const t = type as Named; + const name = t.name; + if (isWidlType(name.value)) { + return { type: name.value }; + } else { + const link = getAnnotation('provider', annotations); + if (name.value === 'link') { + if (link) { + const provider = link.arguments[0]; + return { + type: 'link', + provider: provider.value.getValue(), + }; + } else { + return { + type: 'link', + }; + } + } else { + return { type: 'ref', ref: `#/types/${name.value}` }; + } + } + } + case Kind.MapType: { + const t = type as MapType; + return { + type: 'map', + key: reduceType(t.keyType, annotations), + value: reduceType(t.valueType, annotations), + }; + } + case Kind.ListType: { + const t = type as ListType; + return { + type: 'list', + element: reduceType(t.type, annotations), + }; + } + case Kind.Optional: { + const t = type as Optional; + return { + type: 'optional', + option: reduceType(t.type, annotations), + }; } - } else { - return codegen(node); } + throw new Error(`Unhandled type: ${type.getKind()}`); } -function interpret(doc: Document): Component { +function interpret(doc: Document): [ComponentSignature, Record] { const types = doc.definitions.filter(isType); const input_def = findByName(types, 'Inputs'); const output_def = findByName(types, 'Outputs'); @@ -113,17 +155,30 @@ function interpret(doc: Document): Component { if (!input_def) throw new Error('Component schemas must include a type definition named "Inputs"'); if (!output_def) throw new Error('Component schemas must include a type definition named "Outputs"'); - return { + const inputs: TypeMap = Object.fromEntries( + input_def.fields.map(field => { + return [field.name.value, reduceType(field.type, field.annotations)]; + }), + ); + + const outputs = Object.fromEntries( + output_def.fields.map(field => { + return [field.name.value, reduceType(field.type, field.annotations)]; + }), + ); + + const component: ComponentSignature = { name: (namespace as NamespaceDefinition).name.value, - inputs: input_def.fields.map(field => ({ - name: field.name.value, - type_string: distillType(types, field.type as Named), - })), - outputs: output_def.fields.map(field => ({ - name: field.name.value, - type_string: distillType(types, field.type as Named), - })), + inputs, + outputs, }; + + const typeSignatures = Object.fromEntries( + types + .filter(t => t.name.value !== 'Inputs' && t.name.value !== 'Outputs') + .map(t => [t.name.value, reduceTypeDefinition(t)]), + ); + return [component, typeSignatures]; } export function handler(args: Arguments): void { @@ -135,14 +190,28 @@ export function handler(args: Arguments): void { const files = fs.readdirSync(args.schema_dir).filter(path => path.endsWith('.widl')); - const components = files.map(file => { + const components: Record = {}; + let types: Record = {}; + + const resolver = (location: string) => { + const pathParts = location.split('/'); + const importPath = path.join(args.schema_dir, ...pathParts); + const src = readFile(importPath); + return src; + }; + + for (const file of files) { const widlSrc = readFile(path.join(args.schema_dir, file)); - const tree = parse(widlSrc); - const component = interpret(tree); - return component; - }); - const providerSignature = { + const tree = parse(widlSrc, resolver); + const [component, additionalTypes] = interpret(tree); + types = Object.assign(types, additionalTypes); + + components[component.name] = component; + } + + const providerSignature: ProviderSignature = { name: args.name, + types, components, }; diff --git a/src/languages/rust/provider-component-module.ts b/src/languages/rust/provider-component-module.ts index 00db679..d50c4fe 100644 --- a/src/languages/rust/provider-component-module.ts +++ b/src/languages/rust/provider-component-module.ts @@ -10,7 +10,6 @@ import { outputOpts, CommonWidlOptions, normalizeFilename, - registerCommonPartials, } from '../../common'; import fs from 'fs'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..a581f9f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,91 @@ +export type WIDL_TYPE = + | 'i8' + | 'u8' + | 'i16' + | 'u16' + | 'i32' + | 'u32' + | 'i64' + | 'u64' + | 'f32' + | 'f64' + | 'bool' + | 'string' + | 'datetime' + | 'bytes' + | 'raw' + | 'value'; + +export const WIDL_TYPE_LIST = [ + 'i8', + 'u8', + 'i16', + 'u16', + 'i32', + 'u32', + 'i64', + 'u64', + 'f32', + 'f64', + 'bool', + 'string', + 'datetime', + 'bytes', + 'raw', + 'value', +]; + +export type TypeMap = Record; + +export type TypeSignature = SimpleType | RefType | ListType | OptionalType | MapType | LinkType; + +export interface ProviderSignature { + name: string; + types: Record; + components: Record; +} + +export interface ComponentSignature { + name: string; + inputs: TypeMap; + outputs: TypeMap; +} + +export interface StructSignature { + name: string; + fields: Record; +} + +export interface SimpleType { + type: WIDL_TYPE; +} + +export interface RefType { + type: 'ref'; + ref: string; +} + +export interface MapType { + type: 'map'; + key: TypeSignature; + value: TypeSignature; +} + +export interface ListType { + type: 'list'; + element: TypeSignature; +} + +export interface OptionalType { + type: 'optional'; + option: TypeSignature; +} + +export interface LinkType { + type: 'link'; + provider?: string; +} + +export function isWidlType(type: string): type is WIDL_TYPE { + return WIDL_TYPE_LIST.includes(type); +} diff --git a/templates/rust/interface.hbs b/templates/rust/interface.hbs index 2bd350d..55900ab 100644 --- a/templates/rust/interface.hbs +++ b/templates/rust/interface.hbs @@ -8,17 +8,21 @@ Deserialize, Serialize, }; + use std::collections::HashMap; + #[cfg(feature = "native")] pub use vino_provider::native::prelude::*; + #[cfg(feature = "wasm")] + pub use vino_provider::wasm::prelude::*; {{#with document}} - #[must_use] + #[cfg(any(feature = "native", feature = "wasm"))] pub fn signature() -> ComponentSignature { ComponentSignature { name: "{{namespace.name.value}}".to_owned(), - inputs : PortSignature::from_list(inputs_list()), - outputs : PortSignature::from_list(outputs_list()), + inputs : inputs_list().into(), + outputs : outputs_list().into(), } } diff --git a/templates/rust/partials/common/expand-type.hbs b/templates/rust/partials/common/expand-type.hbs index 756685e..959e21a 100644 --- a/templates/rust/partials/common/expand-type.hbs +++ b/templates/rust/partials/common/expand-type.hbs @@ -4,6 +4,8 @@ {{#switch name.value}} {{#case "string"}}String{{/case}} {{#case "bytes"}}Vec{{/case}} + {{#case "raw"}}RawPacket{{/case}} + {{#case "link"}}ProviderLink{{/case}} {{#default}}{{name.value}}{{/default}} {{/switch}} {{/case}} diff --git a/templates/rust/partials/common/native-inputs.hbs b/templates/rust/partials/common/native-inputs.hbs index eba6bbe..6dab80b 100644 --- a/templates/rust/partials/common/native-inputs.hbs +++ b/templates/rust/partials/common/native-inputs.hbs @@ -1,13 +1,18 @@ +#[cfg(any(feature = "native", feature = "wasm"))] pub fn populate_inputs(mut payload: TransportMap) -> Result { Ok(Inputs { {{#each fields }} - {{snakeCase name.value}}: payload.consume("{{name.value}}")?, + {{#ifCond type.name.value "==" "raw"}} + {{snakeCase name.value}}: payload.consume_raw("{{name.value}}")?.into(), + {{else}} + {{snakeCase name.value}}: payload.consume("{{name.value}}")?, + {{/ifCond}} {{/each}} }) } -#[derive(Debug, Deserialize, Serialize, Default, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub struct Inputs { {{#each fields }} #[serde(rename = "{{name.value}}")] @@ -15,19 +20,28 @@ pub struct Inputs { {{/each}} } +#[cfg(any(feature = "native", feature = "wasm"))] impl From for TransportMap { fn from(inputs: Inputs) -> TransportMap { let mut map = TransportMap::new(); {{#each fields }} - map.insert("{{snakeCase name.value}}".to_owned(), MessageTransport::success(&inputs.{{snakeCase name.value}})); + {{#ifCond type.name.value "==" "raw"}} + map.insert("{{snakeCase name.value}}".to_owned(), inputs.{{snakeCase name.value}}.into()); + {{else}} + map.insert("{{snakeCase name.value}}".to_owned(), MessageTransport::success(&inputs.{{snakeCase name.value}})); + {{/ifCond}} + {{/each}} map } } -static INPUTS_LIST: &[(&str, &str)] = &[{{#join fields ","}}("{{name.value}}", "{{codegen-type type}}"){{/join}}]; - #[must_use] -pub fn inputs_list() -> &'static [(&'static str, &'static str)] { - INPUTS_LIST -} +#[cfg(any(feature = "native", feature = "wasm"))] +pub fn inputs_list() -> HashMap { + let mut map = HashMap::new(); + {{#each fields}} + map.insert("{{name.value}}".to_owned(),{{> type-signature type}}); + {{/each}} + map +} \ No newline at end of file diff --git a/templates/rust/partials/common/native-outputs.hbs b/templates/rust/partials/common/native-outputs.hbs index fedea09..71a1b20 100644 --- a/templates/rust/partials/common/native-outputs.hbs +++ b/templates/rust/partials/common/native-outputs.hbs @@ -1,24 +1,30 @@ #[derive(Debug, Default)] -pub struct Outputs { +#[cfg(feature = "provider")] +pub struct OutputPorts { {{#each fields}} pub {{snakeCase name.value}}: {{pascalCase name.value}}PortSender, {{/each}} } -static OUTPUTS_LIST: &[(&str, &str)] = &[{{#join fields ","}}("{{name.value}}", "{{codegen-type type}}"){{/join}}]; - #[must_use] -pub fn outputs_list() -> &'static [(&'static str, &'static str)] { - OUTPUTS_LIST +#[cfg(any(feature = "native", feature = "wasm"))] +pub fn outputs_list() -> HashMap { + let mut map = HashMap::new(); + {{#each fields}} + map.insert("{{name.value}}".to_owned(),{{> type-signature type}}); + {{/each}} + map } {{#each fields}} #[derive(Debug)] +#[cfg(feature = "provider")] pub struct {{pascalCase name.value}}PortSender { port: PortChannel, } +#[cfg(feature = "provider")] impl Default for {{pascalCase name.value}}PortSender { fn default() -> Self { Self { @@ -26,9 +32,9 @@ impl Default for {{pascalCase name.value}}PortSender { } } } -impl PortSender for {{pascalCase name.value}}PortSender { - type PayloadType = {{>expand-type type}}; +#[cfg(feature = "provider")] +impl PortSender for {{pascalCase name.value}}PortSender { fn get_port(&self) -> Result<&PortChannel, ProviderError> { if self.port.is_closed() { Err(ProviderError::SendChannelClosed) @@ -44,8 +50,9 @@ impl PortSender for {{pascalCase name.value}}PortSender { {{/each}} #[must_use] -pub fn get_outputs() -> (Outputs, TransportStream) { - let mut outputs = Outputs::default(); +#[cfg(feature = "provider")] +pub fn get_outputs() -> (OutputPorts, TransportStream) { + let mut outputs = OutputPorts::default(); let mut ports = vec![ {{#each fields}} &mut outputs.{{snakeCase name.value}}.port, @@ -53,4 +60,46 @@ pub fn get_outputs() -> (Outputs, TransportStream) { ]; let stream = PortChannel::merge_all(&mut ports); (outputs, stream) +} + + +#[cfg(all(feature = "guest"))] +#[allow(missing_debug_implementations)] +pub struct Outputs { + packets: ProviderOutput +} + +#[cfg(all(feature = "native", feature = "guest"))] +impl Outputs { + {{#each fields}} + pub async fn {{snakeCase name.value}}(&mut self)-> Result expand-type type }}>, ProviderError> { + let packets = self.packets.take("{{name.value}}").await; + Ok(PortOutput::new("{{name.value}}".to_owned(), packets)) + } + {{/each}} +} + +#[cfg(all(feature = "wasm", feature = "guest"))] +impl Outputs { + {{#each fields}} + pub fn {{snakeCase name.value}}(&mut self)-> Result { + let packets = self.packets.take("{{name.value}}").ok_or_else(|| WasmError::ResponseMissing("{{name.value}}".to_owned()))?; + Ok(PortOutput::new("{{name.value}}".to_owned(), packets)) + } + {{/each}} +} + +#[cfg(all(feature = "wasm", feature = "guest"))] +impl From for Outputs { + fn from(packets: ProviderOutput) -> Self { + Self{packets} + } +} + + +#[cfg(all(feature = "native", feature = "guest"))] +impl From for Outputs { + fn from(stream: BoxedTransportStream) -> Self { + Self{packets: ProviderOutput::new(stream)} + } } \ No newline at end of file diff --git a/templates/rust/partials/common/type-signature.hbs b/templates/rust/partials/common/type-signature.hbs new file mode 100644 index 0000000..09c9192 --- /dev/null +++ b/templates/rust/partials/common/type-signature.hbs @@ -0,0 +1,45 @@ +{{#switch kind ~}} + {{#case "Named"}} + {{#switch name.value}} + {{#case "i8"}}TypeSignature::I8{{/case}} + {{#case "u8"}}TypeSignature::U8{{/case}} + {{#case "i16"}}TypeSignature::I16{{/case}} + {{#case "u16"}}TypeSignature::U16{{/case}} + {{#case "i32"}}TypeSignature::I32{{/case}} + {{#case "u32"}}TypeSignature::U32{{/case}} + {{#case "i64"}}TypeSignature::I64{{/case}} + {{#case "u64"}}TypeSignature::U64{{/case}} + {{#case "f32"}}TypeSignature::F32{{/case}} + {{#case "f64"}}TypeSignature::F64{{/case}} + {{#case "bool"}}TypeSignature::Bool{{/case}} + {{#case "string"}}TypeSignature::String{{/case}} + {{#case "datetime"}}TypeSignature::Datetime{{/case}} + {{#case "bytes"}}TypeSignature::Bytes{{/case}} + {{#case "raw"}}TypeSignature::Raw{{/case}} + {{#case "value"}}TypeSignature::Value{{/case}} + + {{#case "link"}}Link{provider:None}{{/case}} + + {{#default}}{{name.value}}{{/default}} + {{/switch}} + {{/case}} + {{#case "MapType"}} + TypeSignature::Map{ + key:{{> type-signature keyType}}.into(), + value:{{> type-signature valueType }}.into() + } + {{/case}} + {{#case "ListType"}} + TypeSignature::List{ + element:{{> type-signature type}}.into(), + } + {{/case}} + {{#case "Optional"}} + TypeSignature::Optional{ + option:{{> type-signature type}}.into(), + } + {{/case}} + {{#default}} + unknown + {{/default}} +{{~/switch}} diff --git a/templates/rust/partials/wapc-integration/Inputs.hbs b/templates/rust/partials/wapc-integration/Inputs.hbs index 37c81cc..89c89cd 100644 --- a/templates/rust/partials/wapc-integration/Inputs.hbs +++ b/templates/rust/partials/wapc-integration/Inputs.hbs @@ -1,4 +1,5 @@ + fn populate_inputs(payload: &IncomingPayload) -> Result { Ok(Inputs { {{#each fields }} @@ -7,10 +8,28 @@ fn populate_inputs(payload: &IncomingPayload) -> Result { }) } -#[derive(Debug, Deserialize, Serialize, Default, Clone)] -pub(crate) struct Inputs { +impl From for TransportMap { + fn from(inputs: Inputs) -> TransportMap { + let mut map = TransportMap::new(); + {{#each fields }} + {{#switch type.name.value}} + {{#case "raw"}} + map.insert("{{snakeCase name.value}}".to_owned(), inputs.{{snakeCase name.value}}.into()); + {{/case}} + {{#default}} + map.insert("{{snakeCase name.value}}".to_owned(), MessageTransport::success(&inputs.{{snakeCase name.value}})); + {{/default}} + {{/switch}} + {{/each}} + map + } +} + +#[derive(Debug,Deserialize, Serialize, Clone)] +pub struct Inputs { {{#each fields }} #[serde(rename = "{{name.value}}")] pub {{snakeCase name.value}}: {{> expand-type type}}, {{/each}} -} \ No newline at end of file +} + diff --git a/templates/rust/partials/wapc-integration/Outputs.hbs b/templates/rust/partials/wapc-integration/Outputs.hbs index bcd7460..8d4847d 100644 --- a/templates/rust/partials/wapc-integration/Outputs.hbs +++ b/templates/rust/partials/wapc-integration/Outputs.hbs @@ -1,26 +1,50 @@ -fn get_outputs() -> Outputs { - Outputs { - {{#each fields}} - {{snakeCase name.value}}: GuestPort{{pascalCase name.value}} { }, - {{/each}} - } + +#[derive(Debug)] +pub struct OutputPorts { +{{#each fields}} + pub {{snakeCase name.value}}: {{pascalCase name.value}}Sender, +{{/each}} } {{#each fields}} #[derive(Debug, PartialEq, Clone)] -pub struct GuestPort{{pascalCase name.value}} {} +pub struct {{pascalCase name.value}}Sender { id: u32 } -impl PortSender for GuestPort{{pascalCase name.value}} { +impl PortSender for {{pascalCase name.value}}Sender { type PayloadType = {{> expand-type type}}; fn get_name(&self) -> String { "{{name.value}}".to_string() } + fn get_id(&self) -> u32 { + self.id + } } {{/each}} +fn get_outputs(id:u32) -> OutputPorts { + OutputPorts { + {{#each fields}} + {{snakeCase name.value}}: {{pascalCase name.value}}Sender { id }, + {{/each}} + } +} + #[derive(Debug)] pub struct Outputs { -{{#each fields}} - pub {{snakeCase name.value}}: GuestPort{{pascalCase name.value}}, -{{/each}} + packets: ProviderOutput +} + +impl Outputs { + {{#each fields}} + pub fn {{snakeCase name.value}}(&mut self)-> Result { + let packets = self.packets.take("{{name.value}}").ok_or_else(||ComponentError::new("No packets for port '{{name.value}}' found"))?; + Ok(PortOutput::new("{{name.value}}".to_owned(), packets)) + } + {{/each}} } + +impl From for Outputs { + fn from(packets: ProviderOutput) -> Self { + Self{packets} + } +} \ No newline at end of file diff --git a/templates/rust/partials/wapc-integration/TypeDefinition.hbs b/templates/rust/partials/wapc-integration/TypeDefinition.hbs index c40fb88..5737e63 100644 --- a/templates/rust/partials/wapc-integration/TypeDefinition.hbs +++ b/templates/rust/partials/wapc-integration/TypeDefinition.hbs @@ -16,4 +16,4 @@ {{/each}} } {{/default}} -{{/switch}} \ No newline at end of file +{{/switch}} diff --git a/templates/rust/partials/wellknown-integration/component.hbs b/templates/rust/partials/wellknown-integration/component.hbs index f96ac41..208b82c 100644 --- a/templates/rust/partials/wellknown-integration/component.hbs +++ b/templates/rust/partials/wellknown-integration/component.hbs @@ -17,7 +17,7 @@ impl NativeComponent for Component { context: Self::Context, data: TransportMap, ) -> Result> { - let inputs = populate_inputs(data).map_err(|e| NativeComponentError::new(format!("Input deserialization error: {}", e.to_string())))?; + let inputs = populate_inputs(data).map_err(|e| NativeComponentError::new(e.to_string()))?; let (outputs, stream) = get_outputs(); let result = crate::components::{{snakeCase name}}::job(inputs, outputs, context).await; match result { diff --git a/templates/rust/provider-component.hbs b/templates/rust/provider-component.hbs index 16fede8..b690a3d 100644 --- a/templates/rust/provider-component.hbs +++ b/templates/rust/provider-component.hbs @@ -3,7 +3,7 @@ use crate::generated::{{snakeCase schema.unhyphenated}}::*; pub(crate) async fn job( input: Inputs, - output: Outputs, + output: OutputPorts, context: crate::Context, ) -> JobResult { Ok(()) diff --git a/templates/rust/provider-integration.hbs b/templates/rust/provider-integration.hbs index 4024d68..37cf084 100644 --- a/templates/rust/provider-integration.hbs +++ b/templates/rust/provider-integration.hbs @@ -30,12 +30,19 @@ impl Dispatch for Dispatcher { } } -pub(crate) fn get_all_components() -> Vec { - vec![ +pub(crate) fn get_signature() -> ProviderSignature { + use std::collections::HashMap; + let mut components = HashMap::new(); + {{#each schemas}} - generated::{{snakeCase file.unhyphenated}}::signature(), + components.insert("{{document.namespace.name.value}}".to_owned(), generated::{{snakeCase file.unhyphenated}}::signature()); {{/each}} - ] + + ProviderSignature { + name: "".to_owned(), + types: StructMap::todo(), + components: components.into() + } } {{#each schemas}} @@ -49,13 +56,17 @@ pub (crate) mod {{snakeCase file.unhyphenated}} { Serialize, }; - pub(crate) use vino_provider::native::prelude::*; + #[cfg(feature = "native")] + pub use vino_provider::native::prelude::*; + + #[cfg(feature = "wasm")] + pub use vino_provider::wasm::prelude::*; pub(crate) fn signature() -> ComponentSignature { ComponentSignature { name: "{{document.namespace.name.value }}".to_owned(), - inputs : PortSignature::from_list(inputs_list()), - outputs : PortSignature::from_list(outputs_list()), + inputs : inputs_list().into(), + outputs : outputs_list().into(), } } @@ -71,13 +82,13 @@ pub (crate) mod {{snakeCase file.unhyphenated}} { context: Self::Context, data: TransportMap, ) -> Result> { - let inputs = populate_inputs(data).map_err(|e| NativeComponentError::new(format!("Input deserialization error: {}", e.to_string())))?; + let inputs = populate_inputs(data).map_err(|e| NativeComponentError::new(e.to_string()))?; let (outputs, stream) = get_outputs(); let result = tokio::spawn(crate::components::{{snakeCase namespace.name.value}}::job(inputs, outputs, context)) .await .map_err(|e| { Box::new(NativeComponentError::new(format!( - "Component panicked: {}", + "Component error: {}", e ))) })?; diff --git a/templates/rust/wapc-component.hbs b/templates/rust/wapc-component.hbs index 18f2e58..f43776e 100644 --- a/templates/rust/wapc-component.hbs +++ b/templates/rust/wapc-component.hbs @@ -2,6 +2,6 @@ use crate::generated::{{snakeCase schema.unhyphenated}}::*; -pub(crate) fn job(input: Inputs, output: Outputs) -> JobResult { +pub(crate) fn job(input: Inputs, output: OutputPorts) -> JobResult { Ok(()) } diff --git a/templates/rust/wapc-integration.hbs b/templates/rust/wapc-integration.hbs index 0d7943f..a02b4fc 100644 --- a/templates/rust/wapc-integration.hbs +++ b/templates/rust/wapc-integration.hbs @@ -4,7 +4,7 @@ use vino_provider::wasm::prelude::*; -type Result = std::result::Result; +type Result = std::result::Result; #[no_mangle] pub(crate) extern "C" fn __guest_call(op_len: i32, req_len: i32) -> i32 { @@ -55,25 +55,25 @@ impl Dispatch for Dispatcher { {{#each schemas}} "{{document.namespace.name.value}}" => {{snakeCase file.unhyphenated}}::Component::new().execute(&payload), {{/each}} - _ => Err(Error::ComponentNotFound(op.to_owned(), ALL_COMPONENTS.join(", "))), + _ => Err(WasmError::ComponentNotFound(op.to_owned(), ALL_COMPONENTS.join(", "))), }?; Ok(serialize(&result)?) } } {{#each schemas}} -pub (crate) mod {{snakeCase file.unhyphenated}} { +pub mod {{snakeCase file.unhyphenated}} { use crate::components::{{snakeCase file.unhyphenated}} as implementation; use serde::{ Deserialize, Serialize, }; - pub use vino_provider::wasm::{JobResult, console_log, PortSender}; + pub use vino_provider::wasm::prelude::*; use super::*; - pub(crate) struct Component {} + pub struct Component {} impl Component { pub fn new() -> Self { @@ -82,8 +82,8 @@ pub (crate) mod {{snakeCase file.unhyphenated}} { } impl WapcComponent for Component { fn execute(&self, payload: &IncomingPayload) -> JobResult { + let outputs = get_outputs(payload.id()); let inputs = populate_inputs(payload)?; - let outputs = get_outputs(); implementation::job(inputs, outputs) } } diff --git a/templates/rust/wapc-lib.hbs b/templates/rust/wapc-lib.hbs index a5fd8ad..98cd248 100644 --- a/templates/rust/wapc-lib.hbs +++ b/templates/rust/wapc-lib.hbs @@ -1,7 +1,3 @@ mod generated; -mod components; +pub mod components; -#[no_mangle] -pub fn wapc_init() { - generated::register_handlers(); -} diff --git a/templates/rust/wellknown-integration.hbs b/templates/rust/wellknown-integration.hbs index ac672ce..332556b 100644 --- a/templates/rust/wellknown-integration.hbs +++ b/templates/rust/wellknown-integration.hbs @@ -4,12 +4,23 @@ pub(crate) use vino_provider::native::prelude::*; -pub(crate) fn get_all_components() -> Vec { - vec![ +pub(crate) fn get_signature() -> ProviderSignature { + use std::collections::HashMap; + let mut components = HashMap::new(); {{#each interface.components}} - {{snakeCase @root.interface.name}}::{{snakeCase name}}::signature(), + components.insert("{{name}}".to_owned(), {{snakeCase @root.interface.name}}::{{snakeCase name}}::signature()); {{/each}} - ] + {{#each schemas}} + components.insert("{{document.namespace.name.value}}".to_owned(), generated::{{snakeCase file.unhyphenated}}::signature()); + {{/each}} + + + ProviderSignature { + name: "".to_owned(), + types: StructMap::todo(), + components: components.into() + } + } #[derive(Debug)] diff --git a/test/fixtures/hello-world.widl b/test/fixtures/hello-world.widl index a619a8e..53b9942 100644 --- a/test/fixtures/hello-world.widl +++ b/test/fixtures/hello-world.widl @@ -9,4 +9,5 @@ type Inputs { "Example outputs" type Outputs { greeting: string -} \ No newline at end of file +} + diff --git a/test/fixtures/http.widl b/test/fixtures/http.widl new file mode 100644 index 0000000..e0d1019 --- /dev/null +++ b/test/fixtures/http.widl @@ -0,0 +1,12 @@ +namespace "http-request" + +import {HttpRequest,HttpResponse} from "./include/types.widl" + +type Inputs { + request: HttpRequest +} + +type Outputs { + response: HttpResponse +} + diff --git a/test/fixtures/include/types.widl b/test/fixtures/include/types.widl new file mode 100644 index 0000000..7c6b90a --- /dev/null +++ b/test/fixtures/include/types.widl @@ -0,0 +1,10 @@ +type HttpResponse { + body:string + headers: {string: string} +} + +type HttpRequest { + url: string + method: string + link: link @provider("http") +} \ No newline at end of file diff --git a/test/json.test.ts b/test/json.test.ts new file mode 100644 index 0000000..df2f053 --- /dev/null +++ b/test/json.test.ts @@ -0,0 +1,72 @@ +import { expect } from 'chai'; +import { describe } from 'mocha'; +import path from 'path'; +import fs from 'fs'; +import os from 'os'; + +import { handler } from '../src/languages/json/json'; +import { ProviderSignature } from '../src/types'; + +describe('json command', function () { + it('should generate default interface schema json', () => { + const root = path.join(__dirname, 'fixtures'); + const bignum = process.hrtime.bigint().toString(); + const filepath = path.join(os.tmpdir(), `${bignum}.json`); + handler({ force: false, name: 'test-name', root, schema_dir: root, silent: false, output: filepath }); + const contents = fs.readFileSync(filepath, 'utf-8'); + const json = JSON.parse(contents); + + const expected: ProviderSignature = { + name: 'test-name', + types: { + HttpRequest: { + name: 'HttpRequest', + fields: { + url: { type: 'string' }, + method: { type: 'string' }, + link: { type: 'link', provider: 'http' }, + }, + }, + HttpResponse: { + name: 'HttpResponse', + fields: { + body: { type: 'string' }, + headers: { type: 'map', key: { type: 'string' }, value: { type: 'string' } }, + }, + }, + }, + components: { + add: { + name: 'add', + inputs: { + left: { type: 'i64' }, + right: { type: 'i64' }, + }, + outputs: { + sum: { type: 'i64' }, + }, + }, + 'hello-world': { + name: 'hello-world', + inputs: { + message: { type: 'string' }, + }, + outputs: { + greeting: { type: 'string' }, + }, + }, + 'http-request': { + name: 'http-request', + inputs: { + request: { type: 'ref', ref: '#/types/HttpRequest' }, + }, + outputs: { + response: { type: 'ref', ref: '#/types/HttpResponse' }, + }, + }, + }, + }; + + expect(json).to.deep.equal(expected); + }); +});