diff --git a/implementations/app-swift/.gitignore b/implementations/app-swift/.gitignore new file mode 100644 index 00000000..20591e12 --- /dev/null +++ b/implementations/app-swift/.gitignore @@ -0,0 +1,6 @@ +node_modules +.polywrap +target +workflows/output.json +wrap +debug \ No newline at end of file diff --git a/implementations/app-swift/.nvmrc b/implementations/app-swift/.nvmrc new file mode 100644 index 00000000..2f3b977c --- /dev/null +++ b/implementations/app-swift/.nvmrc @@ -0,0 +1 @@ +v18.15.0 \ No newline at end of file diff --git a/implementations/app-swift/Cargo.lock b/implementations/app-swift/Cargo.lock new file mode 100644 index 00000000..873d7ff6 --- /dev/null +++ b/implementations/app-swift/Cargo.lock @@ -0,0 +1,371 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "app-swift-wrap-abi-bindgen" +version = "0.1.0" +dependencies = [ + "handlebars", + "lazy_static", + "polywrap-wasm-rs", + "polywrap_msgpack_serde", + "serde", + "serde_json", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "handlebars" +version = "4.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pest" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f94bca7e7a599d89dea5dfa309e217e7906c3c007fb9c3299c40b10d6a315d3" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "polywrap-wasm-rs" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d86fefea3844fbd61bfe218bec7131b0aca2eb4b8f12f36b5c33b84d737207" +dependencies = [ + "bigdecimal", + "byteorder", + "num-bigint", + "num-traits", + "serde_json", + "thiserror", +] + +[[package]] +name = "polywrap_msgpack_serde" +version = "0.0.2-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb55e4b83dd7fb9d547792aa65d68698e3b16b3c0e26bb2846c576b4c994c9f" +dependencies = [ + "bigdecimal", + "byteorder", + "num-bigint", + "serde", + "serde_bytes", + "serde_json", + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/implementations/app-swift/Cargo.toml b/implementations/app-swift/Cargo.toml new file mode 100644 index 00000000..61014862 --- /dev/null +++ b/implementations/app-swift/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "app-swift-wrap-abi-bindgen" +version = "0.1.0" +description = "Generates Swift app bindings from a WRAP ABI" +authors = ["Polywrap"] +repository = "https://github.com/polywrap/wrap-abi-bindgen" +license = "MIT" +edition = "2021" + +[dependencies] +polywrap-wasm-rs = { version = "~0.11.0" } +polywrap_msgpack_serde = { version = "0.0.2-beta.5" } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0.97" } +handlebars = { version = "4.3.7" } +lazy_static = { version = "1.4.0" } + +[lib] +crate-type = ["cdylib"] + +[profile.release] +opt-level = 's' +lto = true +panic = 'abort' diff --git a/implementations/app-swift/URI.txt b/implementations/app-swift/URI.txt new file mode 100644 index 00000000..73b57090 --- /dev/null +++ b/implementations/app-swift/URI.txt @@ -0,0 +1 @@ +wrap://ipfs/QmQAnYYNyzDQjgcBcceAMgfyKzRVwHxTYpAtUrsSRuThpT \ No newline at end of file diff --git a/implementations/app-swift/build/wrap.info b/implementations/app-swift/build/wrap.info new file mode 100644 index 00000000..aef5cd44 Binary files /dev/null and b/implementations/app-swift/build/wrap.info differ diff --git a/implementations/app-swift/build/wrap.wasm b/implementations/app-swift/build/wrap.wasm new file mode 100755 index 00000000..ebb8d88b Binary files /dev/null and b/implementations/app-swift/build/wrap.wasm differ diff --git a/implementations/app-swift/jest.config.js b/implementations/app-swift/jest.config.js new file mode 100644 index 00000000..4a4c022f --- /dev/null +++ b/implementations/app-swift/jest.config.js @@ -0,0 +1,15 @@ +module.exports = { + collectCoverage: false, + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/__tests__/**/?(*.)+(spec|test).[jt]s?(x)"], + globals: { + "ts-jest": { + tsconfig: "tsconfig.json", + diagnostics: false, + }, + }, + testPathIgnorePatterns: [ + "/.polywrap/" + ], +}; diff --git a/implementations/app-swift/package.json b/implementations/app-swift/package.json new file mode 100644 index 00000000..a4bd63e9 --- /dev/null +++ b/implementations/app-swift/package.json @@ -0,0 +1,20 @@ +{ + "name": "app-swift-wrap-abi-bindgen", + "private": true, + "scripts": { + "codegen": "npx polywrap codegen", + "build": "npx polywrap build", + "deploy": "npx polywrap deploy", + "test": "yarn test:codegen && jest --passWithNoTests --runInBand --verbose", + "test:codegen": "npx polywrap codegen -m ./src/__tests__/polywrap.yaml -g ./src/__tests__/wrap" + }, + "devDependencies": { + "@types/jest": "26.0.8", + "@polywrap/schema-parse": "~0.11.1", + "jest": "26.6.3", + "jest-diff": "26.6.2", + "polywrap": "~0.11.1", + "ts-jest": "26.5.4", + "typescript": "4.9.5" + } +} diff --git a/implementations/app-swift/polywrap.deploy.yaml b/implementations/app-swift/polywrap.deploy.yaml new file mode 100644 index 00000000..2b9ab565 --- /dev/null +++ b/implementations/app-swift/polywrap.deploy.yaml @@ -0,0 +1,7 @@ +format: 0.1.0 +stages: + ipfs_deploy: + package: ipfs + uri: fs/./build + config: + gatewayUri: "https://ipfs.wrappers.io" \ No newline at end of file diff --git a/implementations/app-swift/polywrap.graphql b/implementations/app-swift/polywrap.graphql new file mode 100644 index 00000000..3704ef4a --- /dev/null +++ b/implementations/app-swift/polywrap.graphql @@ -0,0 +1 @@ +#import * from "wrap://ens/wraps.eth:abi-bindgen@0.1" diff --git a/implementations/app-swift/polywrap.yaml b/implementations/app-swift/polywrap.yaml new file mode 100644 index 00000000..4ae58d9b --- /dev/null +++ b/implementations/app-swift/polywrap.yaml @@ -0,0 +1,10 @@ +format: 0.3.0 +project: + name: app-swift-wrap-abi-bindgen + type: wasm/rust +source: + module: ./Cargo.toml + schema: ./polywrap.graphql + import_abis: + - uri: wrap://ens/wraps.eth:abi-bindgen@0.1 + abi: ../../interface/polywrap.graphql diff --git a/implementations/app-swift/src/__tests__/cases/000-sanity/input.graphql b/implementations/app-swift/src/__tests__/cases/000-sanity/input.graphql new file mode 100644 index 00000000..2755c645 --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/000-sanity/input.graphql @@ -0,0 +1,255 @@ +### Polywrap Header START ### +scalar UInt +scalar UInt8 +scalar UInt16 +scalar UInt32 +scalar Int +scalar Int8 +scalar Int16 +scalar Int32 +scalar Bytes +scalar BigInt +scalar BigNumber +scalar JSON +scalar Map + +directive @imported( + uri: String! + namespace: String! + nativeType: String! +) on OBJECT | ENUM + +directive @imports( + types: [String!]! +) on OBJECT + +directive @capability( + type: String! + uri: String! + namespace: String! +) repeatable on OBJECT + +directive @enabled_interface on OBJECT + +directive @annotate(type: String!) on FIELD + +### Polywrap Header END ### + +type Module @imports( + types: [ + "TestImport_Module", + "TestImport_Object", + "TestImport_AnotherObject", + "TestImport_Enum", + "TestImport_Enum_Return" + ] +) @capability( + type: "getImplementations", + uri: "testimport.uri.eth", + namespace: "TestImport" +) { + moduleMethod( + str: String! + optStr: String + en: CustomEnum! + optEnum: CustomEnum + enumArray: [CustomEnum!]! + optEnumArray: [CustomEnum] + map: Map! @annotate(type: "Map!") + mapOfArr: Map! @annotate(type: "Map!") + mapOfMap: Map! @annotate(type: "Map!>!") + mapOfObj: Map! @annotate(type: "Map!") + mapOfArrOfObj: Map! @annotate(type: "Map!") + ): Int! + + objectMethod( + object: AnotherType! + optObject: AnotherType + objectArray: [AnotherType!]! + optObjectArray: [AnotherType] + ): AnotherType @env(required: true) + + optionalEnvMethod( + object: AnotherType! + optObject: AnotherType + objectArray: [AnotherType!]! + optObjectArray: [AnotherType] + ): AnotherType @env(required: false) + + if( + if: else! + ): else! +} + +type Env { + prop: String! + optProp: String + optMap: Map @annotate(type: "Map") +} + +type CustomType { + str: String! + optStr: String + u: UInt! + optU: UInt + u8: UInt8! + u16: UInt16! + u32: UInt32! + i: Int! + i8: Int8! + i16: Int16! + i32: Int32! + bigint: BigInt! + optBigint: BigInt + bignumber: BigNumber! + optBignumber: BigNumber + json: JSON! + optJson: JSON + bytes: Bytes! + optBytes: Bytes + boolean: Boolean! + optBoolean: Boolean + u_array: [UInt!]! + uOpt_array: [UInt!] + _opt_uOptArray: [UInt] + optStrOptArray: [String] + uArrayArray: [[UInt!]!]! + uOptArrayOptArray: [[UInt32]]! + uArrayOptArrayArray: [[[UInt32!]!]]! + crazyArray: [[[[UInt32!]]!]] + object: AnotherType! + optObject: AnotherType + objectArray: [AnotherType!]! + optObjectArray: [AnotherType] + en: CustomEnum! + optEnum: CustomEnum + enumArray: [CustomEnum!]! + optEnumArray: [CustomEnum] + map: Map! @annotate(type: "Map!") + mapOfArr: Map! @annotate(type: "Map!") + mapOfObj: Map! @annotate(type: "Map!") + mapOfArrOfObj: Map! @annotate(type: "Map!") + mapCustomValue: Map! @annotate(type: "Map!") +} + +type AnotherType { + prop: String + circular: CustomType + const: String +} + +enum CustomEnum { + STRING + BYTES +} + +type CustomMapValue { + foo: String! +} + +type else { + else: String! +} + +enum while { + for, + in, +} + +### Imported Modules START ### + +type TestImport_Module @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "Module" +) @enabled_interface { + importedMethod( + str: String! + optStr: String + u: UInt! + optU: UInt + uArrayArray: [[UInt]]! + object: TestImport_Object! + optObject: TestImport_Object + objectArray: [TestImport_Object!]! + optObjectArray: [TestImport_Object] + en: TestImport_Enum! + optEnum: TestImport_Enum + enumArray: [TestImport_Enum!]! + optEnumArray: [TestImport_Enum] + ): TestImport_Object @env(required: true) + + anotherMethod( + arg: [String!]! + ): Int32! + + returnsArrayOfEnums( + arg: String! + ): [TestImport_Enum_Return]! +} + +### Imported Modules END ### + +### Imported Objects START ### + +type TestImport_Object @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "Object" +) { + object: TestImport_AnotherObject! + optObject: TestImport_AnotherObject + objectArray: [TestImport_AnotherObject!]! + optObjectArray: [TestImport_AnotherObject] + en: TestImport_Enum! + optEnum: TestImport_Enum + enumArray: [TestImport_Enum!]! + optEnumArray: [TestImport_Enum] +} + +type TestImport_AnotherObject @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "AnotherObject" +) { + prop: String! +} + +enum TestImport_Enum @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "Enum" +) { + STRING + BYTES +} + +enum TestImport_Enum_Return @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "Enum" +) { + STRING + BYTES +} + +### Imported Objects END ### + +### Imported Envs START ### + +type TestImport_Env @imported( + uri: "testimport.uri.eth", + namespace: "TestImport", + nativeType: "Env" +) { + object: TestImport_AnotherObject! + optObject: TestImport_AnotherObject + objectArray: [TestImport_AnotherObject!]! + optObjectArray: [TestImport_AnotherObject] + en: TestImport_Enum! + optEnum: TestImport_Enum + enumArray: [TestImport_Enum!]! + optEnumArray: [TestImport_Enum] +} + +### Imported Envs END ### \ No newline at end of file diff --git a/implementations/app-swift/src/__tests__/cases/000-sanity/output/Modules.swift b/implementations/app-swift/src/__tests__/cases/000-sanity/output/Modules.swift new file mode 100644 index 00000000..b53afc16 --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/000-sanity/output/Modules.swift @@ -0,0 +1,122 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Imported modules START // + +// URI: "testimport.uri.eth" // +public struct TestImportArgsImportedMethod: Codable { + var str: String + var optStr: String? + var u: UInt32 + var optU: UInt32? + var uArrayArray: Array?> + var object: TestImportObject + var optObject: TestImportObject? + var objectArray: Array + var optObjectArray: Array? + var en: TestImportEnum + var optEnum: TestImportEnum? + var enumArray: Array + var optEnumArray: Array? +} + +// URI: "testimport.uri.eth" // +public struct TestImportArgsAnotherMethod: Codable { + var arg: Array +} + +// URI: "testimport.uri.eth" // +public struct TestImportArgsReturnsArrayOfEnums: Codable { + var arg: String +} + +/* URI: "testimport.uri.eth" */ +class TestImport { + static let uri: Uri = try! Uri("testimport.uri.eth") + + var client: PolywrapClient? = nil + var env: TestImportEnv? = nil + var uri: Uri? = nil + + init(client: PolywrapClient? = nil, env: TestImportEnv? = nil, uri: Uri? = nil) { + self.client = client + self.env = env + self.uri = uri + } + + func getDefaultClient() -> PolywrapClient { + if let client = self.client { + return client + } else { + let newClient = BuilderConfig().addSystemDefault().addWeb3Default().build() + self.client = newClient + return newClient + } + } + + func getDefaultUri() -> Uri { + if let uri = self.uri { + return uri + } else { + let newUri = TestImport.uri + self.uri = newUri + return newUri + } + } + + func importedMethod( + args: TestImportArgsImportedMethod, + client: PolywrapClient? = nil, + env: TestImportEnv? = nil, + uri: Uri? = nil + ) throws -> TestImportObject? { + let _client = client ?? self.client ?? getDefaultClient() + let _uri = uri ?? self.uri ?? getDefaultUri() + let _env = env ?? self.env + return try _client.invoke( + uri: _uri, + method: "importedMethod", + args: args, + env: _env + ) + } + + func anotherMethod( + args: TestImportArgsAnotherMethod, + client: PolywrapClient? = nil, + env: TestImportEnv? = nil, + uri: Uri? = nil + ) throws -> Int32 { + let _client = client ?? self.client ?? getDefaultClient() + let _uri = uri ?? self.uri ?? getDefaultUri() + let _env = env ?? self.env + return try _client.invoke( + uri: _uri, + method: "anotherMethod", + args: args, + env: _env + ) + } + + func returnsArrayOfEnums( + args: TestImportArgsReturnsArrayOfEnums, + client: PolywrapClient? = nil, + env: TestImportEnv? = nil, + uri: Uri? = nil + ) throws -> Array { + let _client = client ?? self.client ?? getDefaultClient() + let _uri = uri ?? self.uri ?? getDefaultUri() + let _env = env ?? self.env + return try _client.invoke( + uri: _uri, + method: "returnsArrayOfEnums", + args: args, + env: _env + ) + } +} + +// Imported Modules END // diff --git a/implementations/app-swift/src/__tests__/cases/000-sanity/output/Types.swift b/implementations/app-swift/src/__tests__/cases/000-sanity/output/Types.swift new file mode 100644 index 00000000..b1e6682e --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/000-sanity/output/Types.swift @@ -0,0 +1,133 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Objects START // + +public struct CustomType: Codable { + var str: String + var optStr: String? + var u: UInt32 + var optU: UInt32? + var u8: UInt8 + var u16: UInt16 + var u32: UInt32 + var i: Int32 + var i8: Int8 + var i16: Int16 + var i32: Int32 + var bigint: String + var optBigint: String? + var bignumber: String + var optBignumber: String? + var json: String + var optJson: String? + var bytes: Data + var optBytes: Data? + var boolean: Bool + var optBoolean: Bool? + var u_array: Array + var uOpt_array: Array? + var _opt_uOptArray: Array? + var optStrOptArray: Array? + var uArrayArray: Array> + var uOptArrayOptArray: Array?> + var uArrayOptArrayArray: Array>?> + var crazyArray: Array?>>?>? + var object: AnotherType + var optObject: AnotherType? + var objectArray: Array + var optObjectArray: Array? + var en: CustomEnum + var optEnum: CustomEnum? + var enumArray: Array + var optEnumArray: Array? + var map: [String: Int32] + var mapOfArr: [String: Array] + var mapOfObj: [String: AnotherType] + var mapOfArrOfObj: [String: Array] + var mapCustomValue: [String: CustomMapValue?] +} + +public struct AnotherType: Codable { + var prop: String? + var circular: CustomType? + var const: String? +} + +public struct CustomMapValue: Codable { + var foo: String +} + +public struct Else: Codable { + var else: String +} + + +// Objects END // + +// Enums START // + +public enum CustomEnum: Int, Codable { + case STRING + case BYTES +} + +public enum While: Int, Codable { + case _for + case _in +} + + +// Enums END // + +// Imported objects START // + +public struct TestImportObject: Codable { + var object: TestImportAnotherObject + var optObject: TestImportAnotherObject? + var objectArray: Array + var optObjectArray: Array? + var en: TestImportEnum + var optEnum: TestImportEnum? + var enumArray: Array + var optEnumArray: Array? +} + +public struct TestImportAnotherObject: Codable { + var prop: String +} + + +// Imported objects END // + +// Imported envs START // + +public struct TestImportEnv: Codable { + var object: TestImportAnotherObject + var optObject: TestImportAnotherObject? + var objectArray: Array + var optObjectArray: Array? + var en: TestImportEnum + var optEnum: TestImportEnum? + var enumArray: Array + var optEnumArray: Array? +} + +// Imported envs END // + +// Imported enums START // + +public enum TestImportEnum: Int, Codable { + case STRING + case BYTES +} + +public enum TestImportEnumReturn: Int, Codable { + case STRING + case BYTES +} + +// Imported enums END // diff --git a/implementations/app-swift/src/__tests__/cases/001-module-types/input.graphql b/implementations/app-swift/src/__tests__/cases/001-module-types/input.graphql new file mode 100644 index 00000000..2d6ae2f8 --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/001-module-types/input.graphql @@ -0,0 +1,11 @@ +type Module { + function1( + arg1: UInt32! + arg2: Boolean! + ): String! + + function2( + arg1: Int32 + arg2: Bytes + ): String +} diff --git a/implementations/app-swift/src/__tests__/cases/001-module-types/output/Modules.swift b/implementations/app-swift/src/__tests__/cases/001-module-types/output/Modules.swift new file mode 100644 index 00000000..74b87b2c --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/001-module-types/output/Modules.swift @@ -0,0 +1,9 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Imported modules START // + +// Imported Modules END // diff --git a/implementations/app-swift/src/__tests__/cases/001-module-types/output/Types.swift b/implementations/app-swift/src/__tests__/cases/001-module-types/output/Types.swift new file mode 100644 index 00000000..4b303103 --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/001-module-types/output/Types.swift @@ -0,0 +1,29 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Objects START // + + +// Objects END // + +// Enums START // + + +// Enums END // + +// Imported objects START // + + +// Imported objects END // + +// Imported envs START // + + +// Imported envs END // + +// Imported enums START // + +// Imported enums END // diff --git a/implementations/app-swift/src/__tests__/cases/002-object-types/input.graphql b/implementations/app-swift/src/__tests__/cases/002-object-types/input.graphql new file mode 100644 index 00000000..0aa7c068 --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/002-object-types/input.graphql @@ -0,0 +1,68 @@ +scalar UInt +scalar UInt8 +scalar UInt16 +scalar UInt32 +scalar Int +scalar Int8 +scalar Int16 +scalar Int32 +scalar Bytes +scalar BigInt +scalar BigNumber +scalar JSON +scalar Map + +type CustomType { + str: String! + optStr: String + u: UInt! + optU: UInt + u8: UInt8! + u16: UInt16! + u32: UInt32! + i: Int! + i8: Int8! + i16: Int16! + i32: Int32! + bigint: BigInt! + optBigint: BigInt + bignumber: BigNumber! + optBignumber: BigNumber + json: JSON! + optJson: JSON + bytes: Bytes! + optBytes: Bytes + boolean: Boolean! + optBoolean: Boolean + u_array: [UInt!]! + uOpt_array: [UInt!] + _opt_uOptArray: [UInt] + optStrOptArray: [String] + uArrayArray: [[UInt!]!]! + uOptArrayOptArray: [[UInt32]]! + uArrayOptArrayArray: [[[UInt32!]!]]! + crazyArray: [[[[UInt32!]]!]] + object: AnotherType! + optObject: AnotherType + objectArray: [AnotherType!]! + optObjectArray: [AnotherType] + map: Map! @annotate(type: "Map!") + mapOfArr: Map! @annotate(type: "Map!") + mapOfObj: Map! @annotate(type: "Map!") + mapOfArrOfObj: Map! @annotate(type: "Map!") + mapCustomValue: Map! @annotate(type: "Map!") +} + +type AnotherType { + prop: String + circular: CustomType + const: String +} + +type CustomMapValue { + foo: String! +} + +type else { + else: String! +} diff --git a/implementations/app-swift/src/__tests__/cases/002-object-types/output/Modules.swift b/implementations/app-swift/src/__tests__/cases/002-object-types/output/Modules.swift new file mode 100644 index 00000000..74b87b2c --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/002-object-types/output/Modules.swift @@ -0,0 +1,9 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Imported modules START // + +// Imported Modules END // diff --git a/implementations/app-swift/src/__tests__/cases/002-object-types/output/Types.swift b/implementations/app-swift/src/__tests__/cases/002-object-types/output/Types.swift new file mode 100644 index 00000000..32afc0fb --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/002-object-types/output/Types.swift @@ -0,0 +1,84 @@ +// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Objects START // + +public struct CustomType: Codable { + var str: String + var optStr: String? + var u: UInt32 + var optU: UInt32? + var u8: UInt8 + var u16: UInt16 + var u32: UInt32 + var i: Int32 + var i8: Int8 + var i16: Int16 + var i32: Int32 + var bigint: String + var optBigint: String? + var bignumber: String + var optBignumber: String? + var json: String + var optJson: String? + var bytes: Data + var optBytes: Data? + var boolean: Bool + var optBoolean: Bool? + var u_array: Array + var uOpt_array: Array? + var _opt_uOptArray: Array? + var optStrOptArray: Array? + var uArrayArray: Array> + var uOptArrayOptArray: Array?> + var uArrayOptArrayArray: Array>?> + var crazyArray: Array?>>?>? + var object: AnotherType + var optObject: AnotherType? + var objectArray: Array + var optObjectArray: Array? + var map: [String: Int32] + var mapOfArr: [String: Array] + var mapOfObj: [String: AnotherType] + var mapOfArrOfObj: [String: Array] + var mapCustomValue: [String: CustomMapValue?] +} + +public struct AnotherType: Codable { + var prop: String? + var circular: CustomType? + var const: String? +} + +public struct CustomMapValue: Codable { + var foo: String +} + +public struct Else: Codable { + var else: String +} + + +// Objects END // + +// Enums START // + + +// Enums END // + +// Imported objects START // + + +// Imported objects END // + +// Imported envs START // + + +// Imported envs END // + +// Imported enums START // + +// Imported enums END // diff --git a/implementations/app-swift/src/__tests__/cases/index.ts b/implementations/app-swift/src/__tests__/cases/index.ts new file mode 100644 index 00000000..704f98ab --- /dev/null +++ b/implementations/app-swift/src/__tests__/cases/index.ts @@ -0,0 +1,35 @@ +import { Output } from "../wrap"; +import { loadOutput } from "../output"; + +import fs from "fs"; +import path from "path"; + +export interface TestCase { + name: string; + input: string; + output: Output; +} + +export function loadTestCases(): TestCase[] { + const testCases: TestCase[] = []; + const casesDirents = fs.readdirSync(__dirname, { withFileTypes: true }); + + for (const casesDirent of casesDirents) { + if (!casesDirent.isDirectory()) { + continue; + } + + const caseDir = path.join(__dirname, casesDirent.name); + const caseInputPath = path.join(caseDir, "input.graphql"); + const caseInput = fs.readFileSync(caseInputPath, "utf-8"); + const caseOutputDir = path.join(caseDir, "output"); + const caseOutput = loadOutput(caseOutputDir, ["input.graphql"]); + testCases.push({ + name: casesDirent.name, + input: caseInput, + output: caseOutput + }); + } + + return testCases; +} diff --git a/implementations/app-swift/src/__tests__/e2e.spec.ts b/implementations/app-swift/src/__tests__/e2e.spec.ts new file mode 100644 index 00000000..b9d5e90a --- /dev/null +++ b/implementations/app-swift/src/__tests__/e2e.spec.ts @@ -0,0 +1,62 @@ +import {Output, WrapInfo} from "./wrap"; +import { loadTestCases, TestCase } from "./cases"; +import { orderOutput } from "./output"; + +import { PolywrapClient } from "@polywrap/client-js"; +import { parseSchema } from "@polywrap/schema-parse"; +import diff from "jest-diff"; +import path from "path"; +import fs from "fs"; + +jest.setTimeout(60000); + +describe("e2e", () => { + + const client: PolywrapClient = new PolywrapClient(); + let wrapperUri: string; + let testCases: TestCase[] = loadTestCases(); + + beforeAll(() => { + // Cache wrap URI in build dir + const dirname: string = path.resolve(__dirname); + const wrapperPath: string = path.join(dirname, "..", ".."); + wrapperUri = `fs/${wrapperPath}/build`; + }); + + for (const testCase of testCases) { + it(testCase.name, async () => { + const abi = parseSchema(testCase.input); + + const wrapInfo: WrapInfo = { + version: "0.1", + name: testCase.name, + type: "plugin", + abi: JSON.stringify(abi), + } + + const result = await client.invoke({ + uri: wrapperUri, + method: "generateBindings", + args: { wrapInfo } + }); + + if (!result.ok) fail(result.error); + + const received = orderOutput(result.value); + const expected = orderOutput(testCase.output); + + const debugDir = path.join(__dirname, "debug", testCase.name); + const receivedPath = path.join(debugDir, "received.json"); + const expectedPath = path.join(debugDir, "expected.json"); + fs.mkdirSync(debugDir, { recursive: true }); + fs.writeFileSync(receivedPath, JSON.stringify(received, null, 2)); + fs.writeFileSync(expectedPath, JSON.stringify(expected, null, 2)); + + const differences = diff(expected, received, { expand: false }); + + if (differences && !differences.includes("Compared values have no visual difference")) { + fail(differences); + } + }); + } +}); diff --git a/implementations/app-swift/src/__tests__/output.ts b/implementations/app-swift/src/__tests__/output.ts new file mode 100644 index 00000000..d0d8b12e --- /dev/null +++ b/implementations/app-swift/src/__tests__/output.ts @@ -0,0 +1,80 @@ +import { Output, File, Directory } from "./wrap"; + +import fs from "fs"; +import path from "path"; + +export function orderOutput(output: Output): Output { + orderDirectory(output); + return output; +} + +function orderDirectory(directory: Output | Directory): void { + directory.dirs.sort((a, b) => a.name > b.name ? -1 : 1); + directory.files.sort((a, b) => a.name > b.name ? -1 : 1); + for (const dir of directory.dirs) { + orderDirectory(dir); + } +} + +export function loadOutput(dir: string, ignore: string[]): Output { + const output: Output = { + files: [], + dirs: [] + }; + + const outputDirents = fs.readdirSync(dir, { withFileTypes: true }); + + for (const outputDirent of outputDirents) { + if (ignore.includes(outputDirent.name)) { + continue; + } + + if (outputDirent.isDirectory()) { + output.dirs.push(loadDirectory(dir, outputDirent.name, ignore)); + } else { + output.files.push(loadFile(dir, outputDirent.name)); + } + } + + return output; +} + +function loadFile(dir: string, name: string): File { + const filePath = path.join(dir, name); + const fileData = fs.readFileSync(filePath, "utf-8"); + return { + name: name, + data: fileData + }; +} + +function loadDirectory(dir: string, name: string, ignore: string[]): Directory { + const directory: Directory = { + name: name, + files: [], + dirs: [] + }; + const directoryPath = path.join(dir, name); + const directoryEnts = fs.readdirSync( + directoryPath, + { withFileTypes: true } + ); + + for (const directoryEnt of directoryEnts) { + if (ignore.includes(directoryEnt.name)) { + continue; + } + + if (directoryEnt.isDirectory()) { + directory.dirs.push( + loadDirectory(directoryPath, directoryEnt.name, ignore) + ); + } else { + directory.files.push( + loadFile(directoryPath, directoryEnt.name) + ); + } + } + + return directory; +} diff --git a/implementations/app-swift/src/__tests__/polywrap.yaml b/implementations/app-swift/src/__tests__/polywrap.yaml new file mode 100644 index 00000000..655f8c41 --- /dev/null +++ b/implementations/app-swift/src/__tests__/polywrap.yaml @@ -0,0 +1,6 @@ +format: 0.3.0 +project: + name: test-types + type: app/typescript +source: + schema: ../../../../interface/polywrap.graphql diff --git a/implementations/app-swift/src/helpers/array_has_length.rs b/implementations/app-swift/src/helpers/array_has_length.rs new file mode 100644 index 00000000..e24aa2dc --- /dev/null +++ b/implementations/app-swift/src/helpers/array_has_length.rs @@ -0,0 +1,6 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(array_has_length: |arr: Value| { + arr.is_array() && arr.as_array().unwrap().len() > 0 +}); diff --git a/implementations/app-swift/src/helpers/array_length.rs b/implementations/app-swift/src/helpers/array_length.rs new file mode 100644 index 00000000..75819d16 --- /dev/null +++ b/implementations/app-swift/src/helpers/array_length.rs @@ -0,0 +1,6 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(array_length: |arr: Value| { + arr.as_array().unwrap().len() +}); diff --git a/implementations/app-swift/src/helpers/detect_keyword.rs b/implementations/app-swift/src/helpers/detect_keyword.rs new file mode 100644 index 00000000..df90e81e --- /dev/null +++ b/implementations/app-swift/src/helpers/detect_keyword.rs @@ -0,0 +1,15 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; +use crate::helpers::is_keyword::_is_keyword; + +handlebars_helper!(detect_keyword: |val: Value| { + let type_val = val.as_str().unwrap(); + _detect_keyword(type_val) +}); + +pub fn _detect_keyword(type_val: &str) -> String { + if _is_keyword(type_val) { + return format!("_{}", type_val); + } + type_val.to_string() +} \ No newline at end of file diff --git a/implementations/app-swift/src/helpers/import_has_env.rs b/implementations/app-swift/src/helpers/import_has_env.rs new file mode 100644 index 00000000..69d19a1a --- /dev/null +++ b/implementations/app-swift/src/helpers/import_has_env.rs @@ -0,0 +1,16 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(import_has_env: |importedEnvTypes: Value, namespace: Value| { + let ns = namespace.as_str().expect("namespace must be a string"); + if let Some(importedEnvs) = importedEnvTypes.as_array() { + importedEnvs.iter().any(|envValue| { + let env = envValue.as_object().expect("expected env to be an object"); + let envNamespaceValue = env.get("namespace").expect("imported envs must have a namespace"); + let envNamespace = envNamespaceValue.as_str().expect("imported envs must have a namespace"); + envNamespace == ns + }) + } else { + false + } +}); \ No newline at end of file diff --git a/implementations/app-swift/src/helpers/is_keyword.rs b/implementations/app-swift/src/helpers/is_keyword.rs new file mode 100644 index 00000000..4e581b90 --- /dev/null +++ b/implementations/app-swift/src/helpers/is_keyword.rs @@ -0,0 +1,26 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(is_keyword: |val: Value| { + let s = val.as_str().unwrap(); + _is_keyword(s) +}); + +pub fn _is_keyword(s: &str) -> bool { + match s { + // Keywords used in declarations + "associatedtype" | "class" | "deinit" | "enum" | "extension" | "func" | "import" | "init" | "inout" | "internal" | "let" | "operator" | "private" | "protocol" | "public" | "static" | "struct" | "subscript" | "typealias" | "var" | + // Keywords used in statements + "break" | "case" | "catch" | "continue" | "default" | "defer" | "do" | "else" | "fallthrough" | "for" | "guard" | "if" | "in" | "repeat" | "return" | "throw" | "switch" | "where" | "while" | + // Keywords used in expressions and types + "Any" | "as" | "await" | "catch" | "false" | "is" | "nil" | "rethrows" | "self" | "Self" | "super" | "throw" | "throws" | "true" | "try" | + // Keywords used in the specific context + "associativity" | "convenience" | "didSet" | "dynamic" | "final" | "get" | "indirect" | "infix" | "lazy" | "left" | "mutating" | "none" | "nonmutating" | "optional" | "override" | "postfix" | "precedence" | "prefix" | + "Protocol" | "required" | "right" | "set" | "some" | "Type" | "unowned" | "weak" | "willSet" | + // Keywords that begin with the number sign + "#available" | "#colorLiteral" | "#column" | "#dsohandle" | "#elseif" | "#else" | "#endif" | "#error" | "#fileID" | "#fileLiteral" | "#filePath" | "#file" | "#function" | "#if" | "#imageLiteral" | "#keyPath" | "#line" | "#selector" | "#sourceLocation" | "#warning" | + // Keyword used in the patterns(_) + "_" => true, + _ => false, + } +} diff --git a/implementations/app-swift/src/helpers/is_not_first.rs b/implementations/app-swift/src/helpers/is_not_first.rs new file mode 100644 index 00000000..3a6b8957 --- /dev/null +++ b/implementations/app-swift/src/helpers/is_not_first.rs @@ -0,0 +1,7 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(is_not_first: |index: Value| { + let index_u64 = index.as_u64().unwrap(); + index_u64 != 0 +}); diff --git a/implementations/app-swift/src/helpers/is_not_last.rs b/implementations/app-swift/src/helpers/is_not_last.rs new file mode 100644 index 00000000..be543cf4 --- /dev/null +++ b/implementations/app-swift/src/helpers/is_not_last.rs @@ -0,0 +1,8 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(is_not_last: |index: Value, array: Value| { + let index_u64 = index.as_u64().unwrap(); + let array_len = array.as_array().unwrap().len() as u64; + array_len != (index_u64 + 1) +}); diff --git a/implementations/app-swift/src/helpers/mod.rs b/implementations/app-swift/src/helpers/mod.rs new file mode 100644 index 00000000..c932ed4c --- /dev/null +++ b/implementations/app-swift/src/helpers/mod.rs @@ -0,0 +1,73 @@ +use handlebars::Handlebars; + +mod array_has_length; +mod array_length; +mod detect_keyword; +mod import_has_env; +mod is_keyword; +mod is_not_first; +mod is_not_last; +mod pretty; +mod remove_module_suffix; +mod to_graphql_type; +mod to_lower; +mod to_upper; +mod to_swift; + +// helpers for helpers +mod util; + +pub fn register(handlebars: &mut Handlebars) -> () { + handlebars.register_helper( + "array_has_length", + Box::new(array_has_length::array_has_length) + ); + handlebars.register_helper( + "array_length", + Box::new(array_length::array_length) + ); + handlebars.register_helper( + "detect_keyword", + Box::new(detect_keyword::detect_keyword) + ); + handlebars.register_helper( + "import_has_env", + Box::new(import_has_env::import_has_env) + ); + handlebars.register_helper( + "is_keyword", + Box::new(is_keyword::is_keyword) + ); + handlebars.register_helper( + "is_not_first", + Box::new(is_not_first::is_not_first) + ); + handlebars.register_helper( + "is_not_last", + Box::new(is_not_last::is_not_last) + ); + handlebars.register_helper( + "pretty", + Box::new(pretty::pretty) + ); + handlebars.register_helper( + "remove_module_suffix", + Box::new(remove_module_suffix::remove_module_suffix) + ); + handlebars.register_helper( + "to_graphql_type", + Box::new(to_graphql_type::to_graphql_type) + ); + handlebars.register_helper( + "to_lower", + Box::new(to_lower::to_lower) + ); + handlebars.register_helper( + "to_upper", + Box::new(to_upper::to_upper) + ); + handlebars.register_helper( + "to_swift", + Box::new(to_swift::to_swift) + ); +} diff --git a/implementations/app-swift/src/helpers/pretty.rs b/implementations/app-swift/src/helpers/pretty.rs new file mode 100644 index 00000000..1c6e6e92 --- /dev/null +++ b/implementations/app-swift/src/helpers/pretty.rs @@ -0,0 +1,6 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; + +handlebars_helper!(pretty: |value: Value| { + serde_json::to_string_pretty(&value).unwrap() +}); diff --git a/implementations/app-swift/src/helpers/remove_module_suffix.rs b/implementations/app-swift/src/helpers/remove_module_suffix.rs new file mode 100644 index 00000000..23ae0871 --- /dev/null +++ b/implementations/app-swift/src/helpers/remove_module_suffix.rs @@ -0,0 +1,15 @@ +use handlebars::handlebars_helper; +use serde_json::Value; + +handlebars_helper!(remove_module_suffix: |val: Value| { + let type_val = val.as_str().unwrap(); + _remove_module_suffix(type_val) +}); + +pub fn _remove_module_suffix(type_val: &str) -> String { + if type_val.ends_with("_Module") { + let new_type_val = type_val.strip_suffix("_Module").unwrap(); + return format!("{}", new_type_val); + } + return format!("{}", type_val); +} \ No newline at end of file diff --git a/implementations/app-swift/src/helpers/to_graphql_type.rs b/implementations/app-swift/src/helpers/to_graphql_type.rs new file mode 100644 index 00000000..e7fb171c --- /dev/null +++ b/implementations/app-swift/src/helpers/to_graphql_type.rs @@ -0,0 +1,177 @@ +use handlebars::handlebars_helper; +use serde_json::{Value, Map}; + +#[derive(Debug)] +pub enum DefinitionKind { + Generic = 0, + Object = 1 << 0, + Any = 1 << 1, + Scalar = 1 << 2, + Enum = 1 << 3, + Array = ((1 << 4) as u32 | DefinitionKind::Any as u32) as isize, + Property = ((1 << 5) as u32 | DefinitionKind::Any as u32) as isize, + Method = 1 << 6, + Module = 1 << 7, + ImportedModule = 1 << 8, + ImportedEnum = ((1 << 9) as u32 | DefinitionKind::Enum as u32) as isize, + ImportedObject = ((1 << 10) as u32 | DefinitionKind::Object as u32) as isize, + InterfaceImplemented = 1 << 11, + UnresolvedObjectOrEnum = 1 << 12, + ObjectRef = 1 << 13, + EnumRef = 1 << 14, + Interface = 1 << 15, + Env = 1 << 16, + MapKey = 1 << 17, + Map = ((1 << 18) as u32 | DefinitionKind::Any as u32) as isize, + ImportedEnv = 1 << 19, +} + +impl From for DefinitionKind { + fn from(value: u32) -> Self { + match value { + v if v == Self::Generic as u32 => Self::Generic, + v if v == Self::Object as u32 => Self::Object, + v if v == Self::Any as u32 => Self::Any, + v if v == Self::Scalar as u32 => Self::Scalar, + v if v == Self::Enum as u32 => Self::Enum, + v if v == Self::Array as u32 => Self::Array, + v if v == Self::Property as u32 => Self::Property, + v if v == Self::Method as u32 => Self::Method, + v if v == Self::Module as u32 => Self::Module, + v if v == Self::ImportedModule as u32 => Self::ImportedModule, + v if v == Self::ImportedEnum as u32 => Self::ImportedEnum, + v if v == Self::ImportedObject as u32 => Self::ImportedObject, + v if v == Self::InterfaceImplemented as u32 => Self::InterfaceImplemented, + v if v == Self::UnresolvedObjectOrEnum as u32 => Self::UnresolvedObjectOrEnum, + v if v == Self::ObjectRef as u32 => Self::ObjectRef, + v if v == Self::EnumRef as u32 => Self::EnumRef, + v if v == Self::Interface as u32 => Self::Interface, + v if v == Self::Env as u32 => Self::Env, + v if v == Self::MapKey as u32 => Self::MapKey, + v if v == Self::Map as u32 => Self::Map, + v if v == Self::ImportedEnv as u32 => Self::ImportedEnv, + _ => panic!("Invalid value for DefinitionKind"), + } + } +} + +fn apply_required(type_str: &str, required: bool) -> String { + format!("{}{}", type_str, match required { + true => "!", + false => "" + }) +} + +fn any_to_graphql(any: &Map, prefixed: bool) -> String { + if let Some(object) = any.get("object") { + return to_graphql_type_fn(object.as_object().unwrap(), prefixed); + } else if let Some(array) = any.get("array") { + return to_graphql_type_fn(array.as_object().unwrap(), prefixed); + } else if let Some(scalar) = any.get("scalar") { + return to_graphql_type_fn(scalar.as_object().unwrap(), prefixed); + } else if let Some(enum_value) = any.get("enum") { + return to_graphql_type_fn(enum_value.as_object().unwrap(), prefixed); + } else if let Some(map) = any.get("map") { + return to_graphql_type_fn(map.as_object().unwrap(), prefixed); + } else { + panic!("anyToGraphQL: Any type is invalid."); + } +} + +pub fn to_graphql_type_fn(obj: &Map, prefixed: bool) -> String { + let type_str = obj.get("type").unwrap().as_str().unwrap(); + let required = match obj.get("required") { + Some(x) => x.as_bool().unwrap(), + None => false + }; + let kind = DefinitionKind::from(obj.get("kind").unwrap().as_u64().unwrap() as u32); + + match kind { + DefinitionKind::Object + | DefinitionKind::ObjectRef + | DefinitionKind::Scalar + | DefinitionKind::ImportedObject => apply_required(type_str, required), + DefinitionKind::Enum + | DefinitionKind::EnumRef + | DefinitionKind::ImportedEnum => { + if prefixed { + apply_required(format!("Enum_{}", type_str).as_str(), required) + } else { + apply_required(type_str, required) + } + } + DefinitionKind::Any | DefinitionKind::Property => { + any_to_graphql(obj, prefixed) + } + DefinitionKind::Array => { + if let Some(item) = obj.get("item") { + apply_required(format!("[{}]", to_graphql_type_fn(item.as_object().unwrap(), prefixed)).as_str(), required) + } else { + panic!( + "toGraphQL: ArrayDefinition's item type is undefined.\n{:?}", + obj + ); + } + } + DefinitionKind::Map => { + if let Some(key) = obj.get("key") { + if let Some(value) = obj.get("value") { + apply_required( + format!( + "Map<{}, {}>", + to_graphql_type_fn(key.as_object().unwrap(), prefixed), + to_graphql_type_fn(value.as_object().unwrap(), prefixed) + ).as_str(), + required, + ) + } else { + panic!( + "toGraphQL: MapDefinition's value type is undefined.\n{:?}", + obj + ); + } + } else { + panic!( + "toGraphQL: MapDefinition's key type is undefined.\n{:?}", + obj + ); + } + } + DefinitionKind::Method => { + if let Some(return_type) = obj.get("returnType") { + let arguments = obj.get("arguments").unwrap(); + let result = format!( + "{}(\n {}\n): {}", + obj.get("name").unwrap().as_str().unwrap(), + arguments + .as_array().unwrap() + .into_iter() + .map(|arg| { + let arg_obj = arg.as_object().unwrap(); + format!("{}: {}", arg_obj.get("name").unwrap().as_str().unwrap(), to_graphql_type_fn(arg_obj, prefixed)) + }) + .collect::>() + .join("\n "), + to_graphql_type_fn(return_type.as_object().unwrap(), prefixed) + ); + result + } else { + panic!( + "toGraphQL: MethodDefinition's return type is undefined.\n{:?}", + obj + ); + } + } + DefinitionKind::Module => obj.get("type").unwrap().as_str().unwrap().to_string(), + DefinitionKind::ImportedModule => obj.get("type").unwrap().as_str().unwrap().to_string(), + _ => panic!( + "toGraphQL: Unrecognized DefinitionKind.\n{:?}", + obj + ), + } +} + +handlebars_helper!(to_graphql_type: |value: Value| { + let obj = value.as_object().unwrap(); + to_graphql_type_fn(&obj, true) +}); diff --git a/implementations/app-swift/src/helpers/to_lower.rs b/implementations/app-swift/src/helpers/to_lower.rs new file mode 100644 index 00000000..81794c9e --- /dev/null +++ b/implementations/app-swift/src/helpers/to_lower.rs @@ -0,0 +1,25 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; +use crate::helpers::util::{insert_at, replace_at}; + +handlebars_helper!(to_lower: |val: Value| { + let str = val.as_str().unwrap(); + _to_lower(str) +}); + +pub fn _to_lower(s: &str) -> String { + let mut result = s.to_string(); + let mut i = 0; + while i < result.len() { + let char = result.chars().nth(i).unwrap(); + if char.is_uppercase() { + let lower = char.to_lowercase().collect::(); + result = replace_at(&result, i, &lower); + if i != 0 && result.chars().nth(i - 1).unwrap() != '_' { + result = insert_at(&result, i, "_"); + } + } + i += 1; + } + result +} diff --git a/implementations/app-swift/src/helpers/to_swift.rs b/implementations/app-swift/src/helpers/to_swift.rs new file mode 100644 index 00000000..58aa79d7 --- /dev/null +++ b/implementations/app-swift/src/helpers/to_swift.rs @@ -0,0 +1,74 @@ +use crate::helpers::detect_keyword::_detect_keyword; +use crate::helpers::to_upper::_to_upper; +use crate::helpers::util::{array_type, map_types}; +use handlebars::handlebars_helper; +use serde_json::Value; + +handlebars_helper!(to_swift: |val: Value| { + let type_val = val.as_str().unwrap(); + _to_swift(type_val) +}); + +pub fn _to_swift(value: &str) -> String { + let mut res = value.to_string(); + let mut optional = false; + if res.ends_with("!") { + res.pop(); + } else { + optional = true; + } + + if res.starts_with("[") { + return _to_swift_array(&res, optional).unwrap(); + } + + if res.starts_with("Map<") { + return to_swift_map(&res, optional).unwrap(); + } + + res = match res.as_str() { + "Int" | "Int32" => "Int32".to_string(), + "Int8" => "Int8".to_string(), + "Int16" => "Int16".to_string(), + "Int64" => "Int64".to_string(), + "UInt" | "UInt32" => "UInt32".to_string(), + "UInt8" => "UInt8".to_string(), + "UInt16" => "UInt16".to_string(), + "UInt64" => "UInt64".to_string(), + "Boolean" => "Bool".to_string(), + "Bytes" => "Data".to_string(), + "String" | "BigInt" | "BigNumber" | "JSON" => "String".to_string(), + _ => { + if res.starts_with("Enum_") { + res = res.replacen("Enum_", "", 1); + } + res = _to_upper(&res); + _detect_keyword(&res) + } + }; + + _apply_optional(&res, optional) +} + +pub fn _to_swift_array(value: &str, optional: bool) -> Result { + let inner_type = array_type(value)?; + let swift_type = _to_swift(&inner_type); + let rs_array = format!("Array<{}>", swift_type); + Ok(_apply_optional(&rs_array, optional)) +} + +pub fn to_swift_map(value: &str, optional: bool) -> Result { + let (key_type, val_type) = map_types(value)?; + let swift_key_type = _to_swift(&key_type); + let swift_val_type = _to_swift(&val_type); + let swift_map = format!("[{}: {}]", &swift_key_type, &swift_val_type); + Ok(_apply_optional(&swift_map, optional)) +} + +pub fn _apply_optional(value: &str, optional: bool) -> String { + return if optional { + format!("{}?", value) + } else { + value.to_string() + }; +} diff --git a/implementations/app-swift/src/helpers/to_upper.rs b/implementations/app-swift/src/helpers/to_upper.rs new file mode 100644 index 00000000..c1c6f20f --- /dev/null +++ b/implementations/app-swift/src/helpers/to_upper.rs @@ -0,0 +1,27 @@ +use handlebars::handlebars_helper; +use serde_json::{Value}; +use crate::helpers::util::{remove_at, replace_at}; + +handlebars_helper!(to_upper: |val: Value| { + let str = val.as_str().unwrap(); + _to_upper(str) +}); + +pub fn _to_upper(s: &str) -> String { + let mut result = s.to_string(); + let first_char = result.chars().nth(0).unwrap_or_else(|| panic!("Tried to call to_upper on an empty string")); + let first_upper = first_char.to_uppercase().collect::(); + result = replace_at(&result, 0, &first_upper); + let mut i = 0; + while i < result.len() { + if let Some('_') = result.chars().nth(i) { + if let Some(next_char) = result.chars().nth(i + 1) { + let next_char_upper = next_char.to_uppercase().collect::(); + result = replace_at(&result, i + 1, &next_char_upper); + result = remove_at(&result, i); + } + } + i += 1; + } + result +} diff --git a/implementations/app-swift/src/helpers/util.rs b/implementations/app-swift/src/helpers/util.rs new file mode 100644 index 00000000..9fc89e82 --- /dev/null +++ b/implementations/app-swift/src/helpers/util.rs @@ -0,0 +1,57 @@ +pub fn replace_at(s: &str, idx: usize, replacement: &str) -> String { + let start = s[..idx].to_string(); + let end = s[idx + replacement.len()..].to_string(); + format!("{}{}{}", start, replacement, end) +} + +pub fn insert_at(s: &str, idx: usize, insert: &str) -> String { + let start = s[..idx].to_string(); + let end = s[idx..].to_string(); + format!("{}{}{}", start, insert, end) +} + +pub fn remove_at(s: &str, idx: usize) -> String { + let start = s[..idx].to_string(); + let end = s[idx + 1..].to_string(); + format!("{}{}", start, end) +} + +pub fn array_type(value: &str) -> Result { + let mut iter = value.char_indices(); + + let first_bracket = match iter.find(|&(_, c)| c == '[').map(|(i, _)| i) { + Some(idx) => idx, + None => return Err(format!("Invalid Array: {}", value)), + }; + let last_bracket = match iter.rfind(|&(_, c)| c == ']').map(|(i, _)| i) { + Some(idx) => idx, + None => return Err(format!("Invalid Array: {}", value)), + }; + + let inner_type = &value[(first_bracket+1)..last_bracket]; + Ok(inner_type.to_string()) +} + +pub fn map_types(value: &str) -> Result<(String, String), String> { + let first_open_bracket_idx = match value.find('<') { + Some(idx) => idx, + None => return Err(format!("Invalid Map: {}", value)), + }; + let last_close_bracket_idx = match value.rfind('>') { + Some(idx) => idx, + None => return Err(format!("Invalid Map: {}", value)), + }; + + let key_val_types = &value[(first_open_bracket_idx + 1)..last_close_bracket_idx]; + + let first_comma_idx = match key_val_types.find(',') { + Some(idx) => idx, + None => return Err(format!("Invalid Map: {}", value)), + }; + + let key_type = key_val_types[..first_comma_idx].trim(); + let val_type = key_val_types[(first_comma_idx + 1)..].trim(); + + Ok((key_type.to_string(), val_type.to_string())) +} + diff --git a/implementations/app-swift/src/lib.rs b/implementations/app-swift/src/lib.rs new file mode 100644 index 00000000..bdd202f9 --- /dev/null +++ b/implementations/app-swift/src/lib.rs @@ -0,0 +1,48 @@ +#[macro_use] +extern crate lazy_static; + +pub mod wrap; + +use polywrap_wasm_rs::JSON; +pub use wrap::*; + +pub mod templates; +pub mod helpers; +mod renderer; +use renderer::Renderer; + +impl ModuleTrait for Module { + fn generate_bindings(args: ArgsGenerateBindings) -> Result { + let version = &args.wrap_info.version; + + // First, ensure version is "0.1" + if version != "0.1" { + return Err( + format!("Unsupported ABI Version - {}; Supported - 0.1", version) + ); + } + + let wrap_info = args.wrap_info; + let renderer = Renderer::new(); + let mut output = Output::new(); + let abi = wrap_info.abi.to_json(); + + output.files.push(File { + name: "Modules.swift".to_string(), + data: renderer.render( + "Modules.swift", + &abi + ) + }); + + output.files.push(File { + name: "Types.swift".to_string(), + data: renderer.render( + "Types.swift", + &abi + ) + }); + + Ok(output) + } +} diff --git a/implementations/app-swift/src/renderer.rs b/implementations/app-swift/src/renderer.rs new file mode 100644 index 00000000..07b091a1 --- /dev/null +++ b/implementations/app-swift/src/renderer.rs @@ -0,0 +1,44 @@ +use handlebars::{ + Handlebars, + no_escape +}; +use serde::Serialize; + +use crate::templates; +use crate::helpers; + +pub struct Renderer<'reg> { + instance: Handlebars<'reg> +} + +impl<'reg> Renderer<'reg> { + pub fn new() -> Renderer<'reg> { + let mut handlebars: Handlebars = Handlebars::new(); + + // Remove the HTML escape function + handlebars.register_escape_fn(no_escape); + + // Register all templates + let templates = templates::load_templates(); + for template in templates.iter() { + handlebars.register_template_string( + &template.name, + &template.source + ).unwrap(); + } + + // Register all helpers + helpers::register(&mut handlebars); + + Renderer { + instance: handlebars + } + } + + pub fn render(self: &Renderer<'reg>, name: &str, data: &T) -> String + where + T: Serialize, + { + self.instance.render(name, data).unwrap() + } +} diff --git a/implementations/app-swift/src/templates/Modules_swift.rs b/implementations/app-swift/src/templates/Modules_swift.rs new file mode 100644 index 00000000..1cfea159 --- /dev/null +++ b/implementations/app-swift/src/templates/Modules_swift.rs @@ -0,0 +1,101 @@ +lazy_static! { + static ref NAME: String = "Modules.swift".to_string(); + static ref SOURCE: String = r#"// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Imported modules START // + +{{#each importedModuleTypes}} +{{#each methods}} +// URI: "{{../uri}}" // +public struct {{to_upper (remove_module_suffix ../type)}}Args{{to_upper name}}: Codable { + {{#each arguments}} + var {{ name }}: {{to_swift (to_graphql_type this)}} + {{/each}} +} + +{{/each}} +/* URI: "{{uri}}" */ +class {{to_upper (remove_module_suffix type)}} { + static let uri: Uri = try! Uri("{{uri}}") + + var client: PolywrapClient? = nil + {{#if (import_has_env ../importedEnvTypes namespace)}} + var env: {{to_upper namespace}}Env? = nil + {{/if}} + var uri: Uri? = nil + + init(client: PolywrapClient? = nil{{#if (import_has_env ../importedEnvTypes namespace)}}, env: {{to_upper namespace}}Env? = nil{{/if}}, uri: Uri? = nil) { + self.client = client + {{#if (import_has_env ../importedEnvTypes namespace)}} + self.env = env + {{/if}} + self.uri = uri + } + + func getDefaultClient() -> PolywrapClient { + if let client = self.client { + return client + } else { + let newClient = BuilderConfig().addSystemDefault().addWeb3Default().build() + self.client = newClient + return newClient + } + } + + func getDefaultUri() -> Uri { + if let uri = self.uri { + return uri + } else { + let newUri = {{to_upper (remove_module_suffix type)}}.uri + self.uri = newUri + return newUri + } + } + {{#each methods}} + + func {{detect_keyword name}}( + args: {{to_upper (remove_module_suffix ../type)}}Args{{to_upper name}}, + client: PolywrapClient? = nil, + {{#if (import_has_env ../../importedEnvTypes ../namespace)}} + env: {{to_upper ../namespace}}Env? = nil, + {{/if}} + uri: Uri? = nil + ) throws -> {{#with return}}{{to_swift (to_graphql_type this)}}{{/with}} { + let _client = client ?? self.client ?? getDefaultClient() + let _uri = uri ?? self.uri ?? getDefaultUri() + {{#if (import_has_env ../../importedEnvTypes ../namespace)}} + let _env = env ?? self.env + return try _client.invoke( + uri: _uri, + method: "{{name}}", + args: args, + env: _env + ) + {{else}} + return try _client.invoke( + uri: _uri, + method: "{{name}}", + args: args + ) + {{/if}} + } + {{/each}} +} + +{{/each}} +// Imported Modules END // +"#.to_string(); +} + +use super::Template; + +pub fn load() -> Template { + Template { + name: &*NAME, + source: &*SOURCE, + } +} diff --git a/implementations/app-swift/src/templates/Types_swift.rs b/implementations/app-swift/src/templates/Types_swift.rs new file mode 100644 index 00000000..209e66b1 --- /dev/null +++ b/implementations/app-swift/src/templates/Types_swift.rs @@ -0,0 +1,82 @@ +lazy_static! { + static ref NAME: String = "Types.swift".to_string(); + static ref SOURCE: String = r#"// NOTE: This is an auto-generated file. +// All modifications will be overwritten. + +import PolywrapClient +import Foundation + +// Objects START // + +{{#each objectTypes}} +public struct {{detect_keyword (to_upper type)}}: Codable { + {{#each properties}} + var {{ name }}: {{to_swift (to_graphql_type this)}} + {{/each}} +} + +{{/each}} + +// Objects END // + +// Enums START // + +{{#each enumTypes}} +public enum {{detect_keyword (to_upper type)}}: Int, Codable { + {{#each constants}} + case {{detect_keyword this}} + {{/each}} +} + +{{/each}} + +// Enums END // + +// Imported objects START // + +{{#each importedObjectTypes}} +public struct {{detect_keyword (to_upper type)}}: Codable { + {{#each properties}} + var {{ name }}: {{to_swift (to_graphql_type this)}} + {{/each}} +} + +{{/each}} + +// Imported objects END // + +// Imported envs START // + +{{#each importedEnvTypes}} +public struct {{detect_keyword (to_upper type)}}: Codable { + {{#each properties}} + var {{ name }}: {{to_swift (to_graphql_type this)}} + {{/each}} +} +{{/each}} + +// Imported envs END // + +// Imported enums START // + +{{#each importedEnumTypes}} +public enum {{detect_keyword (to_upper type)}}: Int, Codable { + {{#each constants}} + case {{detect_keyword this}} + {{/each}} +} + +{{/each}} +// Imported enums END // +"#.to_string(); +} + + use super::Template; + + pub fn load() -> Template { + Template { + name: &*NAME, + source: &*SOURCE, + } + } + \ No newline at end of file diff --git a/implementations/app-swift/src/templates/mod.rs b/implementations/app-swift/src/templates/mod.rs new file mode 100644 index 00000000..c85af59d --- /dev/null +++ b/implementations/app-swift/src/templates/mod.rs @@ -0,0 +1,14 @@ +mod Modules_swift; +mod Types_swift; + +pub struct Template { + pub name: &'static str, + pub source: &'static str, +} + +pub fn load_templates() -> Vec