diff --git a/Falanx.Machinery/FSAstExtensions.fs b/Falanx.Machinery/FSAstExtensions.fs index 1236b94..0d73d23 100644 --- a/Falanx.Machinery/FSAstExtensions.fs +++ b/Falanx.Machinery/FSAstExtensions.fs @@ -63,22 +63,42 @@ namespace Falanx.Machinery Expr = synExpr ValData = SynValData(flags, SynValInfo.Empty, None) } + + static member CreateFromProvidedProperty (pp:ProvidedProperty, ?ommitEnclosingType : Type, ?knownNamespaces: _ Set) = + let ident = + let ident = if pp.IsStatic then pp.Name else (thisPrefix +.+ pp.Name) + LongIdentWithDots.CreateString ident + + let synExpr, _parseTree = + Quotations.ToAst(ProvidedProperty.toExpr pp, ?ommitEnclosingType = ommitEnclosingType, ?knownNamespaces = knownNamespaces) + + let flags = if pp.IsStatic then Some MemberFlags.StaticMember else Some MemberFlags.InstanceMember + + SynMemberDefn.CreateMember + { SynBindingRcd.Null with + Pattern = SynPatRcd.CreateLongIdent(ident, [ ] ) + Expr = synExpr + ValData = SynValData(flags, SynValInfo.Empty, None) + } + type SynFieldRcd with static member CreateFromPropertyInfo(pp: Reflection.PropertyInfo, isMutable, ?ommitEnclosingType) = let typeName = SynType.CreateFromType(pp.PropertyType, ?ommitEnclosingType = ommitEnclosingType) SynFieldRcd.Create(Ident.Create(pp.Name), typeName, isMutable) + + static member CreateFromFieldInfo(fi: Reflection.FieldInfo, isMutable, ?ommitEnclosingType) = + let typeName = SynType.CreateFromType(fi.FieldType, ?ommitEnclosingType = ommitEnclosingType) + SynFieldRcd.Create(Ident.Create(fi.Name), typeName, isMutable) type SynModuleDecl with - static member CreateRecord (pt: ProvidedRecord, ?ommitEnclosingType, ?knownNamespaces) = + static member CreateRecord (pr: ProvidedRecord, ?ommitEnclosingType, ?knownNamespaces) = let recordFields = let props = - pt.GetProperties() - |> Seq.choose (function :? ProvidedProperty as pp -> Some pp | _ -> None) - |> Seq.map (fun pp -> SynFieldRcd.CreateFromPropertyInfo(pp, true, ?ommitEnclosingType = ommitEnclosingType)) - |> Seq.toList + pr.RecordFields + |> List.map (fun pi -> SynFieldRcd.CreateFromPropertyInfo(pi, true, ?ommitEnclosingType = ommitEnclosingType)) props - let interfacesAndMembers = ProvidedTypeDefinition.getMethodOverridesByInterfaceType pt + let interfacesAndMembers = ProvidedTypeDefinition.getMethodOverridesByInterfaceType pr let membersInInterfaces = interfacesAndMembers |> Array.collect (fun (_, m) -> m |> Array.map fst ) |> ResizeArray let interfaces = @@ -91,7 +111,7 @@ namespace Falanx.Machinery ] let staticMethods = - pt.GetMethods() + pr.GetMethods() |> Seq.choose(fun pm -> match pm with | :? ProvidedMethod as pm when pm.IsStatic -> let name = pm.Name @@ -102,7 +122,7 @@ namespace Falanx.Machinery |> Seq.toList let instanceMethodsNotInInterface = - pt.GetMethods() + pr.GetMethods() |> Seq.choose(fun pm -> match pm with | :? ProvidedMethod as pm when not pm.IsStatic && not (membersInInterfaces.Contains pm) -> let name = pm.Name @@ -112,17 +132,32 @@ namespace Falanx.Machinery | _ -> None) |> Seq.toList + let properties = + let recordProperties = pr.RecordFields + let props = + pr.GetProperties() + |> Array.choose (fun prop -> if recordProperties |> List.contains prop + then None + else Some (prop :?> ProvidedProperty)) + props + + let properties = + properties + |> Seq.map (fun pp -> + SynMemberDefn.CreateFromProvidedProperty(pp, ?ommitEnclosingType = ommitEnclosingType, ?knownNamespaces = knownNamespaces)) + let attributes = let cliMutableAttribute = SynModuleDecl.CreateAttribute(LongIdentWithDots.CreateString("CLIMutable"), SynExpr.CreateConst SynConst.Unit, false) [cliMutableAttribute] SynModuleDecl.CreateSimpleType ( - { SynComponentInfoRcd.Create (Ident.CreateLong pt.Name) with - XmlDoc = PreXmlDoc.Create (ProvidedTypeDefinition.getXmlDocs pt) + { SynComponentInfoRcd.Create (Ident.CreateLong pr.Name) with + XmlDoc = PreXmlDoc.Create (ProvidedTypeDefinition.getXmlDocs pr) Attributes = attributes }, SynTypeDefnSimpleReprRecordRcd.Create(recordFields) |> SynTypeDefnSimpleReprRcd.Record, - members = [ yield! staticMethods + members = [ yield! properties + yield! staticMethods yield! instanceMethodsNotInInterface yield! interfaces ] ) diff --git a/Falanx.Machinery/ProvidedAdapter.fs b/Falanx.Machinery/ProvidedAdapter.fs index 2c69b05..ed32f46 100644 --- a/Falanx.Machinery/ProvidedAdapter.fs +++ b/Falanx.Machinery/ProvidedAdapter.fs @@ -4,23 +4,31 @@ open Microsoft.FSharp.Quotations open ProviderImplementation.ProvidedTypes open Falanx.Machinery.Prelude open Falanx.Machinery.Expr +open Utils [] module ProvidedMethod = - open Utils - let toExpr (m:ProvidedMethod) = - - let parameters= + + let parameters = [ if not m.IsStatic then yield Expr.Var <| Var(thisPrefix, m.DeclaringType) - + for p in m.GetParameters() do yield Expr.Var <| Var(p.Name, p.ParameterType) ] - + m.GetInvokeCode |> function Some ik -> ik parameters | _ -> <@@ () @@> + +[] +module ProvidedProperty = + ///Note: this only support Properties with a getter + let toExpr (m:ProvidedProperty) = + let getter = match m.Getter with Some getter -> getter() | _ -> failwith "No getter" + match getter with + | :? ProvidedMethod as pm -> ProvidedMethod.toExpr pm + | _ -> failwith "Unknown type" -[] +[] module ProvidedTypeDefinition = let getXmlDocs (providedType: ProvidedTypeDefinition) = providedType.GetCustomAttributesData() @@ -65,7 +73,32 @@ module ProvidedTypeDefinition = |> Expr.callStatic [Expr.Value name; Expr.box args.[1]], setter))) - property, field + property, field + + let mkRecordPropertyWithField propertyType name readonly = + let field = ProvidedField(Naming.pascalToCamel name, propertyType) + field.SetFieldAttributes(Reflection.FieldAttributes.InitOnly ||| Reflection.FieldAttributes.Private) + let property = + ProvidedRecordProperty( + name, + propertyType, + getterCode = (fun args -> Expr.FieldGet(args.[0], field)), + ?setterCode = + if readonly then None + else Some(fun args -> + let setter = Expr.FieldSet(args.[0], field, args.[1]) + if propertyType.IsValueType || + // None appears to be represented as null. + (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() = typedefof>) + then setter + else + Expr.Sequential( + <@@ argNotNull x x @@> + |> Expr.methoddefof + |> Expr.callStatic [Expr.Value name; Expr.box args.[1]], + setter))) + + property, field module TypeProviderConfig = let makeConfig resolutionFolder runtimeAssembly runtimeAssemblyRefs = diff --git a/Falanx.Machinery/ProvidedTypesExtensions.fs b/Falanx.Machinery/ProvidedTypesExtensions.fs index b1b3906..5a25c92 100644 --- a/Falanx.Machinery/ProvidedTypesExtensions.fs +++ b/Falanx.Machinery/ProvidedTypesExtensions.fs @@ -89,7 +89,18 @@ type ProvidedUnion(isTgt: bool, container:TypeContainer, className: string, getB unionCaseType = unionCaseType } member __.UnionCases = unionCases.ToArray() - + +type ProvidedRecordProperty(isTgt: bool, propertyName: string, attrs: PropertyAttributes, propertyType: Type, isStatic: bool, getter: (unit -> MethodInfo) option, setter: (unit -> MethodInfo) option, indexParameters: ProvidedParameter[], customAttributesData) = + inherit ProvidedProperty(isTgt, propertyName, attrs, propertyType, isStatic, getter, setter, indexParameters, customAttributesData) + + new (propertyName, propertyType, ?getterCode, ?setterCode, ?isStatic, ?indexParameters) = + let isStatic = defaultArg isStatic false + let indexParameters = defaultArg indexParameters [] + let pattrs = (if isStatic then MethodAttributes.Static else enum(0)) ||| MethodAttributes.Public ||| MethodAttributes.SpecialName + let getter = getterCode |> Option.map (fun _ -> ProvidedMethod(false, "get_" + propertyName, pattrs, Array.ofList indexParameters, propertyType, getterCode, [], None, K [| |]) :> MethodInfo) + let setter = setterCode |> Option.map (fun _ -> ProvidedMethod(false, "set_" + propertyName, pattrs, [| yield! indexParameters; yield ProvidedParameter(false, "value",propertyType,isOut=Some false,optionalValue=None) |], typeof, setterCode, [], None, K [| |]) :> MethodInfo) + ProvidedRecordProperty(false, propertyName, PropertyAttributes.None, propertyType, isStatic, Option.map K getter, Option.map K setter, Array.ofList indexParameters, K [| |]) + type ProvidedRecord(isTgt: bool, container:TypeContainer, className: string, getBaseType: (unit -> Type option), attrs: TypeAttributes, getEnumUnderlyingType, staticParams, staticParamsApply, backingDataSource, customAttributesData, nonNullable, hideObjectMethods) = inherit ProvidedTypeDefinition(isTgt, container, className, getBaseType, attrs, getEnumUnderlyingType, staticParams, staticParamsApply, backingDataSource, customAttributesData, nonNullable, hideObjectMethods) @@ -115,6 +126,11 @@ type ProvidedRecord(isTgt: bool, container:TypeContainer, className: string, get override this.GetCustomAttributes(_inherit) = recordAttribs override this.GetCustomAttributes(_attributeType, _inherit) = recordAttribs + + member this.RecordFields = + this.GetProperties() + |> Array.choose (function :? ProvidedRecordProperty as prp -> Some (prp :> PropertyInfo) | _ -> None) + |> Array.toList module ProvidedUnion = let inline private apply (uc: ProvidedUnion) f = uc.UnionCases |> Seq.tryFind f @@ -125,11 +141,4 @@ module ProvidedUnion = apply uc (fun uc -> uc.name = name) let tryGetUnionCaseByPosition position (uc:ProvidedUnion) = - apply uc (fun uc -> uc.position = position) - - -module ProvidedRecord = - let getRecordFields (providedRecord: ProvidedRecord) = - providedRecord.GetProperties() - |> Array.choose (function :? ProvidedProperty as pp -> Some pp | _ -> None) - |> Array.toList + apply uc (fun uc -> uc.position = position) \ No newline at end of file diff --git a/Falanx.Proto.Codec.Json/Codec.fs b/Falanx.Proto.Codec.Json/Codec.fs index a8f84c7..93b9328 100644 --- a/Falanx.Proto.Codec.Json/Codec.fs +++ b/Falanx.Proto.Codec.Json/Codec.fs @@ -11,7 +11,6 @@ open System.Runtime.CompilerServices open ProviderImplementation.ProvidedTypes.UncheckedQuotations open Falanx.Proto.Core.Model open ProviderImplementation.ProvidedTypes -open FSharpPlus open Newtonsoft.Json.Linq open Reflection @@ -36,7 +35,7 @@ module Quotations = for ex in args do yield! traverseForCall ex | Quotations.ExprShape.ShapeVar _ -> () ] -module temp = +module Codec = let cleanUpTypeName (str:string) = let sb = Text.StringBuilder(str) sb.Replace("System.", "") @@ -89,7 +88,7 @@ module temp = let createLambdaRecord (recordType: ProvidedRecord) = //Lambda (u, Lambda (t, NewRecord (Result2, u, t)) - let recordFields = ProvidedRecord.getRecordFields recordType + let recordFields = recordType.RecordFields let recordVars = recordFields |> List.map(fun pi -> Var(pi.Name, pi.PropertyType)) @@ -136,12 +135,6 @@ module temp = let replaceLambdaArgs (mi: MethodInfo) (lambda:Type) = let lambdaReturn = getFunctionReturnType lambda ProvidedTypeBuilder.MakeGenericMethod(mi, [lambda; typeof>; typeof; lambdaReturn; typeof; typeof] ) -// mi.MakeGenericMethod([|lambda -// typeof> -// typeof -// lambdaReturn -// typeof -// typeof|]) //mapping f: 'a -> ('b -> Result<'a,'c>) * ('d -> IReadOnlyDictionary<'e,'f>) @@ -181,9 +174,7 @@ module temp = let callPipeRight (arg:Expr) (func:Expr) = let methodInfoGeneric = Expr.methoddefof<@ (|>) @> let funcTypeReturn = getFunctionReturnType func.Type - let methodInfoTyped = ProvidedTypeBuilder.MakeGenericMethod(methodInfoGeneric, [arg.Type; funcTypeReturn]) - //methodInfoGeneric.MakeGenericMethod([|arg.Type; funcTypeReturn|]) let expr = Expr.CallUnchecked(methodInfoTyped, [arg; func]) expr @@ -202,26 +193,21 @@ module temp = // expr - let callJfieldopt (recordType: Type) propertyInfo (fieldType: Type ) (nextFieldType: Type option) = - // + let callJfieldopt (recordType: Type) (propertyInfo: PropertyInfo) (fieldType: Type ) (nextFieldType: Type) = // fun u t -> { url = u; title= t } // |> mapping -> Option -> Result2, IReadOnlyDictionary, String, Result2, String, JToken> // |> jfieldopt -> Result2> "url" (fun x -> x.url) // |> jfieldopt "title" (fun x -> x.title) - let remainingTypeExpression = - match nextFieldType with - | Some nextFieldType -> - FSharpType.MakeFunctionType(typedefof>.MakeGenericType(nextFieldType), recordType) - | None -> recordType + let jfieldoptMethodInfo = Expr.methoddefof <@ jfieldOpt<_,string,_> x x x @> - // Record ; fieldType; nextFieldType -> Record - let jFieldTypeArguments = [recordType; fieldType; remainingTypeExpression] + + let jFieldTypeArguments = [recordType; fieldType; nextFieldType] let jfieldoptMethodInfoTyped = ProvidedTypeBuilder.MakeGenericMethod(jfieldoptMethodInfo, jFieldTypeArguments) let formattedjfieldoptMethodInfoTyped = sprintf "%s" (jfieldoptMethodInfoTyped.ToString() |> cleanUpTypeName) - let fieldName = Expr.Value "url" + let fieldName = Expr.Value propertyInfo.Name //should ideally be protodecriptor.name let xvar = Var("x", recordType) @@ -231,14 +217,7 @@ module temp = let domain = typeof> let range = let currentField = typedefof>.MakeGenericType(fieldType) - - let functionType = - match nextFieldType with - | Some nextFieldType -> - let nextField = typedefof>.MakeGenericType(nextFieldType) - makeFunctionTypeFromElements [currentField; nextField; recordType] - | None -> - FSharpType.MakeFunctionType(currentField, recordType) + let functionType = FSharpType.MakeFunctionType(currentField, nextFieldType) //IReadOnlyDictionary -> Result -> Option -> Result2, String> //IReadOnlyDictionary -> Result -> Result2, String> @@ -256,13 +235,11 @@ module temp = // Record -> IReadOnlyDictionary let encoderType = FSharpType.MakeFunctionType(recordType, typeof>) - //let formattedEncoderType = sprintf "%a" sprintfsimpleTypeFormatter encoderType let encoder = Var("encode", encoderType) // decoder * encoder // ( IReadOnlyDictionary -> Result -> Option -> Record, String>) * (Record -> IReadOnlyDictionary ) let codecType = FSharpType.MakeTupleType [|decoderType; encoderType|] - //let formattedCodecType = sprintf "%a" sprintfsimpleTypeFormatter codecType let codec = Var("codec", codecType) Expr.Lambda(codec, @@ -362,19 +339,32 @@ module temp = | None -> recordType TypeTemplate.create callJfieldopt3<_,string,_> "callJfieldopt3" [recordType; fieldType; typeC] propertyInfo - let createRecordJFieldOpts recordFields recordType = - let final = recordFields |> List.last - let pairs = recordFields |> List.pairwise - let optionType (o: Type) = - o.GetGenericArguments().[0] + let createRecordJFieldOpts (recordFields: #PropertyInfo list) (recordType: Type) = + + let fieldTypeWithRest = + let rec loop (recordFields: #PropertyInfo list) = + [ + match recordFields with + | [] -> () + | h :: [] -> + yield h, recordType + | h :: t -> + let rest = (t |> List.map (fun pt -> pt.PropertyType)) @ [recordType] + let all = makeFunctionTypeFromElements rest + yield h, all + yield! loop t + | [h] -> yield h, recordType + ] + loop recordFields + + let optionType (o: Type) = o.GetGenericArguments().[0] let jFieldOptions = - pairs - |> List.map (fun (first, second) -> callJfieldopt recordType first (optionType first.PropertyType) (Some (optionType second.PropertyType))) - let finalJField = callJfieldopt recordType final (optionType final.PropertyType) None + fieldTypeWithRest + |> List.map (fun (field, rest) -> + callJfieldopt recordType field (optionType field.PropertyType) rest) - [ yield! jFieldOptions - yield finalJField ] + jFieldOptions let quotationsTypePrinter expr = @@ -407,26 +397,19 @@ module temp = None | _ -> None) expr - let tryCode (typeDescriptor: TypeDescriptor) = + let createJsonObjCodec (typeDescriptor: TypeDescriptor) = let recordType = typeDescriptor.Type :?> ProvidedRecord let lambdaRecord = createLambdaRecord recordType let mapping = callMapping lambdaRecord let pipeLambdaToMapping = callPipeRight lambdaRecord mapping - let recordFields = ProvidedRecord.getRecordFields recordType + let recordFields = recordType.RecordFields let jFieldOpts = createRecordJFieldOpts recordFields recordType let allPipedFunctions = [yield lambdaRecord; yield mapping; yield! jFieldOpts] let foldedFunctions = allPipedFunctions |> List.reduce callPipeRight - -// let firstJfieldOpt = callJfieldopt recordType (Expr.propertyof<@ (x:Result2).url @>) typeof (Some typeof) -// let secondJFieldOpt = callJfieldopt recordType (Expr.propertyof<@ (x:Result2).title @>) typeof None -// -// let pipeLambdaToMappingToFirstJFieldOpt = callPipeRight pipeLambdaToMapping firstJfieldOpt -// let pipeLambdaToMappingToFirstJFieldOptToSecondJFieldOpt = callPipeRight pipeLambdaToMappingToFirstJFieldOpt secondJFieldOpt -// // let qs = ProviderImplementation.ProvidedTypes.QuotationSimplifier(true) // let simplified = qs.Simplify pipeLambdaToMappingToFirstJFieldOptToSecondJFieldOpt @@ -451,5 +434,5 @@ module temp = let def = typedefof,_>> def.MakeGenericType [|typeof>; typeDescriptor.Type :> _ |] - let createJsonObjCodec = ProvidedMethod("JsonObjCodec", [], signatureType, invokeCode = (fun args -> foldedFunctions), isStatic = true ) + let createJsonObjCodec = ProvidedProperty("JsonObjCodec",signatureType, getterCode = (fun args -> foldedFunctions), isStatic = true ) createJsonObjCodec diff --git a/Falanx.Proto.Generator/CreateProto.fs b/Falanx.Proto.Generator/CreateProto.fs index 6df1c30..b1bef1d 100644 --- a/Falanx.Proto.Generator/CreateProto.fs +++ b/Falanx.Proto.Generator/CreateProto.fs @@ -51,17 +51,19 @@ module Proto = let openBinaryCodecPrimitive = SynModuleDecl.CreateOpen (LongIdentWithDots.CreateString "Falanx.Proto.Codec.Binary.Primitives") let knownNamespaces = - [ providedTypeRoot.Namespace - "System" - "Froto.Serialization" - "System.Collections.Generic" - "Falanx.Proto.Codec.Binary" - "Falanx.Proto.Codec.Binary.Primitives" - "Microsoft.FSharp.Core" - "Microsoft.FSharp.Core.Operators" - "Microsoft.FSharp.Collections" - "Microsoft.FSharp.Control" - "Microsoft.FSharp.Text" ] + [ yield providedTypeRoot.Namespace + yield "System" + yield "System.Collections.Generic" + if codecs.Contains Binary then + yield "Froto.Serialization" + yield "Falanx.Proto.Codec.Binary" + yield "Falanx.Proto.Codec.Binary.Primitives" + + yield "Microsoft.FSharp.Core" + yield "Microsoft.FSharp.Core.Operators" + yield "Microsoft.FSharp.Collections" + yield "Microsoft.FSharp.Control" + yield "Microsoft.FSharp.Text" ] |> Set.ofSeq let synTypes = @@ -90,10 +92,11 @@ module Proto = .AddModule( {SynModuleOrNamespaceRcd.CreateNamespace(Ident.CreateLong providedTypeRoot.Namespace) with IsRecursive = true} .AddDeclarations ( [ yield openSystem - yield openFrotoSerialization yield openSystemCollectionsGeneric - yield openBinaryCodec - yield openBinaryCodecPrimitive + if codecs.Contains Binary then + yield openFrotoSerialization + yield openBinaryCodec + yield openBinaryCodecPrimitive yield! synTypes] ) ) ) diff --git a/Falanx.Proto.Generator/TypeGeneration.fs b/Falanx.Proto.Generator/TypeGeneration.fs index 7072c0e..fd0f4c1 100644 --- a/Falanx.Proto.Generator/TypeGeneration.fs +++ b/Falanx.Proto.Generator/TypeGeneration.fs @@ -34,8 +34,10 @@ module TypeGeneration = let property, backingField = match field.Rule with - | Repeated -> ProvidedTypeDefinition.mkPropertyWithField propertyType propertyName true - | _ -> ProvidedTypeDefinition.mkPropertyWithField propertyType propertyName false + | Repeated -> + ProvidedTypeDefinition.mkRecordPropertyWithField propertyType propertyName true + | _ -> + ProvidedTypeDefinition.mkRecordPropertyWithField propertyType propertyName false //apply custom attributes for record field let constructor = typeof.TryGetConstructor([|typeof; typeof |]) @@ -230,7 +232,6 @@ module TypeGeneration = Some(createOneOfDescriptor nestedScope lookup name members) | _ -> None) - for oneOfDescriptor in oneOfDescriptors do providedType.AddMembers [ oneOfDescriptor.OneOfType :> MemberInfo oneOfDescriptor.CaseField :> _ @@ -242,15 +243,14 @@ module TypeGeneration = for prop in properties do providedType.AddMember prop.ProvidedProperty - providedType.AddMember prop.ProvidedField.Value + prop.ProvidedField |> Option.iter providedType.AddMember let maps = message.Parts - |> Seq.choose (function + |> List.choose (function | TMap(name, keyTy, valueTy, position, _) -> Some(createMapDescriptor nestedScope lookup name keyTy valueTy position) | _ -> None) - |> List.ofSeq for map in maps do providedType.AddMember map.ProvidedField @@ -280,7 +280,7 @@ module TypeGeneration = providedType.AddMember serializedLengthMethod providedType.DefineMethodOverride(serializedLengthMethod, typeof.GetMethod("SerializedLength")) | Json -> - let jsonObjCodec = Falanx.Proto.Codec.Json.temp.tryCode typeInfo + let jsonObjCodec = Falanx.Proto.Codec.Json.Codec.createJsonObjCodec typeInfo providedType.AddMember jsonObjCodec ) diff --git a/test/Falanx.IntegrationTests/Sample.fs b/test/Falanx.IntegrationTests/Sample.fs index ab8303c..b8519cf 100644 --- a/test/Falanx.IntegrationTests/Sample.fs +++ b/test/Falanx.IntegrationTests/Sample.fs @@ -19,8 +19,15 @@ let TestRunDirToolDir = TestRunDir/"tool" let NupkgsDir = RepoDir/"artifact"/"nupkg" let NugetOrgV3Url = "https://api.nuget.org/v3/index.json" +let stdOutLines (cmd: Command) = + cmd.Result.StandardOutput + |> fun s -> s.Trim() + |> fun s -> s.Split(Environment.NewLine) + |> List.ofArray + let checkExitCodeZero (cmd: Command) = - Expect.equal 0 cmd.Result.ExitCode "command finished with exit code non-zero." + "command finished with exit code non-zero" + |> Expect.equal cmd.Result.ExitCode 0 let dotnetCmd (fs: FileUtils) args = fs.shellExecRun "dotnet" args @@ -156,14 +163,6 @@ let tests pkgUnderTestVersion = fs.cd outDir outDir - let asLines (s: string) = - s.Split(Environment.NewLine) |> List.ofArray - - let stdOutLines (cmd: Command) = - cmd.Result.StandardOutput - |> fun s -> s.Trim() - |> asLines - let generalTests = testList "general" [ testCase |> withLog "can show help" (fun _ fs -> @@ -224,7 +223,7 @@ let tests pkgUnderTestVersion = testCase |> withLog "can build sample3 json" (fun _ fs -> let testDir = inDir fs "sanity_check_sample3_json" - Tests.skiptest "only json doesnt work yet" + Tests.skiptest "schema with collection and json format doesnt work yet" testDir |> buildExampleWithTemplate fs ``template2 json`` ``sample6 bundle`` @@ -233,7 +232,7 @@ let tests pkgUnderTestVersion = testCase |> withLog "can build sample4 binary+json" (fun _ fs -> let testDir = inDir fs "sanity_check_sample4_binaryjson" - Tests.skiptest "doesnt contains a json serialization/deserialization" + Tests.skiptest "schema with collection and json format doesnt work yet" testDir |> buildExampleWithTemplate fs ``template3 binary+json`` ``sample6 bundle`` @@ -246,6 +245,13 @@ let tests pkgUnderTestVersion = |> buildExampleWithTemplate fs ``template1 binary`` ``sample5 pkg`` ) + testCase |> withLog "can build sample7 json" (fun _ fs -> + let testDir = inDir fs "sanity_check_sample7_json" + + testDir + |> buildExampleWithTemplate fs ``template2 json`` ``sample7 itemLevelOrderHistory`` + ) + ] let sdkIntegrationMocks = @@ -381,7 +387,7 @@ let tests pkgUnderTestVersion = |> Expect.equal text "ItemLevelOrderHistory(client1,sku1,12.3,brandA,product1,45.6)" ) - testCase |> withLog ".net -> scala" (fun _ fs -> + testCase |> withLog ".net -> binary -> scala" (fun _ fs -> let testDir = inDir fs "interop_net_scala" let scalaApp = testDir/"scala-app" @@ -423,7 +429,7 @@ let tests pkgUnderTestVersion = |> Expect.equal text "ItemLevelOrderHistory(clientA,sku12345,78.91,myBrand1,p100,43.21)" ) - testCase |> withLog "scala -> .net" (fun _ fs -> + testCase |> withLog "scala -> binary -> .net" (fun _ fs -> let testDir = inDir fs "interop_scala_net" let scalaApp = testDir/"scala-app2" @@ -472,6 +478,133 @@ let tests pkgUnderTestVersion = "check deserialize" |> Expect.equal textLines expected ) + + testCase |> withLog "scala json sanity check" (fun _ fs -> + let testDir = inDir fs "sanity_check_scala_json" + + let jsonFilePath = testDir/"a.txt" + let outFilePath = testDir/"out.txt" + + // copy the template and add the sample + testDir + |> copyExampleWithTemplate fs ``template5 scala json`` ``sample7 itemLevelOrderHistory`` + + requireSbt () + + fs.cd testDir + + // serialize + sbt_run fs ["--serialize"; jsonFilePath] + |> checkExitCodeZero + + "check serialized file exists" + |> Expect.isTrue (File.Exists jsonFilePath) + + // deserialize + sbt_run fs ["--deserialize"; jsonFilePath; "--out"; outFilePath] + |> checkExitCodeZero + + "check out file exists" + |> Expect.isTrue (File.Exists outFilePath) + + let text = File.ReadAllText(outFilePath) + + "check deserialize" + |> Expect.equal text "ItemLevelOrderHistory(client1,sku1,12.3,brandA,product1,45.6)" + ) + + testCase |> withLog ".net -> json -> scala" (fun _ fs -> + let testDir = inDir fs "interop_net_scala_json" + + let scalaApp = testDir/"scala-app" + let netApp = testDir/"net-app" + + let jsonFilePath = testDir/"a.json" + let outFilePath = testDir/"out.txt" + + // copy the template and add the sample + scalaApp + |> copyExampleWithTemplate fs ``template5 scala json`` ``sample7 itemLevelOrderHistory`` + + netApp + |> copyExampleWithTemplate fs ``template2 json`` ``sample7 itemLevelOrderHistory`` + + // serialize with .net + fs.cd netApp + + dotnetCmd fs ["run"; "-p"; ``template2 json``.AssemblyName; "--"; "--serialize"; jsonFilePath] + |> checkExitCodeZero + + "check serialized file exists" + |> Expect.isTrue (File.Exists jsonFilePath) + + // deserialize with scala + requireSbt () + + fs.cd scalaApp + + sbt_run fs ["--deserialize"; jsonFilePath; "--out"; outFilePath] + |> checkExitCodeZero + + "check out file exists" + |> Expect.isTrue (File.Exists outFilePath) + + let text = File.ReadAllText(outFilePath) + + "check deserialize" + |> Expect.equal text "ItemLevelOrderHistory(clientA,sku12345,78.91,myBrand1,p100,43.21)" + ) + + testCase |> withLog "scala -> json -> .net" (fun _ fs -> + let testDir = inDir fs "interop_scala_net_json" + + let scalaApp = testDir/"scala-app2" + let netApp = testDir/"net-app2" + + let jsonFilePath = testDir/"b.json" + let outFilePath = testDir/"out2.txt" + + // copy the template and add the sample + scalaApp + |> copyExampleWithTemplate fs ``template5 scala json`` ``sample7 itemLevelOrderHistory`` + + netApp + |> copyExampleWithTemplate fs ``template2 json`` ``sample7 itemLevelOrderHistory`` + + // serialize with scala + requireSbt () + + fs.cd scalaApp + + sbt_run fs ["--serialize"; jsonFilePath] + |> checkExitCodeZero + + "check out file exists" + |> Expect.isTrue (File.Exists jsonFilePath) + + // deserialize with .net + fs.cd netApp + + dotnetCmd fs ["run"; "-p"; ``template2 json``.AssemblyName; "--"; "--deserialize"; jsonFilePath; "--out"; outFilePath] + |> checkExitCodeZero + + "check deserialized file exists" + |> Expect.isTrue (File.Exists outFilePath) + + let textLines = File.ReadAllLines(outFilePath) |> List.ofArray + + let expected = + ["""{clientId = Some "client1";""" + """ retailSkuId = Some "sku1";""" + """ categoryId = Some 12.3;""" + """ brand = Some "brandA";""" + """ product = Some "product1";""" + """ orderTss = Some 45.5999985f;}""" ] + + "check deserialize" + |> Expect.equal textLines expected + ) + ] [ generalTests diff --git a/test/Falanx.IntegrationTests/TestAssets.fs b/test/Falanx.IntegrationTests/TestAssets.fs index 94a532e..5dea9e3 100644 --- a/test/Falanx.IntegrationTests/TestAssets.fs +++ b/test/Falanx.IntegrationTests/TestAssets.fs @@ -50,6 +50,14 @@ let ``template4 scala`` = AssemblyName = "src"/"main"/"scala" ProtoFile = "src"/"main"/"protobuf"/"ItemLevelOrderHistory.proto" } +let ``template5 scala json`` = + { ProjDir = "template5 scala json" + Language = Scala + RequiredFormats = [Json] + ProjectFile = "src"/"main"/"scala"/"Program.scala" + AssemblyName = "src"/"main"/"scala" + ProtoFile = "src"/"main"/"protobuf"/"ItemLevelOrderHistory.proto" } + type FalanxMock = { ProjDir: string FileName: string } @@ -75,5 +83,9 @@ let ``sample6 bundle`` = let ``sample7 itemLevelOrderHistory`` = { ExampleDir = "sample7 itemLevelOrderHistory" - FileNames = [FSharp, Binary, "BinaryExample.fs"; Scala, Binary, "BinaryExample.scala"] + FileNames = + [ FSharp, Binary, "BinaryExample.fs" + FSharp, Json, "JsonExample.fs" + Scala, Binary, "BinaryExample.scala" + Scala, Json, "JsonExample.scala" ] ProtoFile = "ItemLevelOrderHistory.proto" } diff --git a/test/examples/sample7 itemLevelOrderHistory/JsonExample.fs b/test/examples/sample7 itemLevelOrderHistory/JsonExample.fs new file mode 100644 index 0000000..a3dc7d7 --- /dev/null +++ b/test/examples/sample7 itemLevelOrderHistory/JsonExample.fs @@ -0,0 +1,24 @@ +module JsonExample + +open l1.Contracts +open Fleece.Newtonsoft + +let serialize () : string = + let r = + { ItemLevelOrderHistory.clientId = Some "clientA" + retailSkuId = Some "sku12345" + categoryId = Some 78.91 + brand = Some "myBrand1" + product = Some "p100" + orderTss = Some 43.21f } + + toJson r |> string + +let deserialize (jsonText: string) : ItemLevelOrderHistory = + + let s = parseJson jsonText + + match s with + | Result.Ok x -> x + | Result.Error err -> failwithf "error parsing json: '%A'" err + diff --git a/test/examples/sample7 itemLevelOrderHistory/JsonExample.scala b/test/examples/sample7 itemLevelOrderHistory/JsonExample.scala new file mode 100644 index 0000000..63c2d2e --- /dev/null +++ b/test/examples/sample7 itemLevelOrderHistory/JsonExample.scala @@ -0,0 +1,21 @@ +class JsonExample { + + def serialize() = { + val item = + ItemLevelOrderHistory() + .withClientId("client1") + .withRetailSkuId("sku1") + .withCategoryId(12.3) + .withBrand("brandA") + .withProduct("product1") + .withOrderTss(45.6f) + + val jsonText: String = scalapb.json4s.JsonFormat.toJsonString(item) + jsonText + } + + def deserialize(jsonText: String) = { + val item2: ItemLevelOrderHistory = scalapb.json4s.JsonFormat.fromJsonString[ItemLevelOrderHistory](jsonText) + item2 + } +} diff --git a/test/examples/template2 json/l1/Program.fs b/test/examples/template2 json/l1/Program.fs index 27e1c77..f234d44 100644 --- a/test/examples/template2 json/l1/Program.fs +++ b/test/examples/template2 json/l1/Program.fs @@ -24,12 +24,12 @@ module Program = printfn "Serialized:" printfn "%A" bytes - File.WriteAllBytes(path, bytes) + File.WriteAllText(path, bytes) | Deserialize (path, outputPath) -> - let bytes = File.ReadAllBytes(path) + let jsonText = File.ReadAllText(path) - let s = JsonExample.deserialize bytes + let s = JsonExample.deserialize jsonText printfn "Deserialized:" printfn "%A" s diff --git a/test/examples/template3 binaryjson/l1/Program.fs b/test/examples/template3 binaryjson/l1/Program.fs index 7c3f9a0..692e36a 100644 --- a/test/examples/template3 binaryjson/l1/Program.fs +++ b/test/examples/template3 binaryjson/l1/Program.fs @@ -15,12 +15,12 @@ module Program = printfn "[BINARY] Deserialized:" printfn "%A" s - let bytes = JsonExample.serialize () + let jsonText = JsonExample.serialize () printfn "[JSON] Serialized:" - printfn "%A" bytes + printfn "%A" jsonText - let s = JsonExample.deserialize bytes + let s = JsonExample.deserialize jsonText printfn "[JSON] Deserialized:" printfn "%A" s diff --git a/test/examples/template5 scala json/build.sbt b/test/examples/template5 scala json/build.sbt new file mode 100644 index 0000000..9980611 --- /dev/null +++ b/test/examples/template5 scala json/build.sbt @@ -0,0 +1,11 @@ +name := "sample7 scala" + +version := "0.1" + +scalaVersion := "2.12.7" + +PB.targets in Compile := Seq( + scalapb.gen(flatPackage = true) -> (sourceManaged in Compile).value +) + +libraryDependencies += "com.thesamet.scalapb" %% "scalapb-json4s" % "0.7.1" diff --git a/test/examples/template5 scala json/project/build.properties b/test/examples/template5 scala json/project/build.properties new file mode 100644 index 0000000..e71780d --- /dev/null +++ b/test/examples/template5 scala json/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.2.6 \ No newline at end of file diff --git a/test/examples/template5 scala json/project/scalapb.sbt b/test/examples/template5 scala json/project/scalapb.sbt new file mode 100644 index 0000000..0771ba3 --- /dev/null +++ b/test/examples/template5 scala json/project/scalapb.sbt @@ -0,0 +1,3 @@ +addSbtPlugin("com.thesamet" % "sbt-protoc" % "0.99.19") + +libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.8.2" diff --git a/test/examples/template5 scala json/src/main/scala/Program.scala b/test/examples/template5 scala json/src/main/scala/Program.scala new file mode 100644 index 0000000..f8e9088 --- /dev/null +++ b/test/examples/template5 scala json/src/main/scala/Program.scala @@ -0,0 +1,40 @@ +import java.nio.file.{Files, Paths} +import java.nio.charset.StandardCharsets +import scala.collection.JavaConverters._ + +object Program { + + sealed trait Command + case class Serialize(path: String) extends Command + case class Deserialize(path: String, out: String) extends Command + + + def main(args: Array[String]): Unit = { + + val cmd = + args match { + case Array("--serialize", p) => Serialize(p) + case Array("--deserialize", p, "--out", o) => Deserialize(p, o) + case _ => throw new Exception("invalid args, expecting --serialize or --deserialize") + } + + val example = new JsonExample + + cmd match { + case Serialize(path) => { + val jsonText = example.serialize() + println(jsonText) + val bytes = jsonText.getBytes(StandardCharsets.UTF_8) + Files.write(Paths.get(path), bytes) + } + case Deserialize(path, out) => { + val lines = Files.readAllLines(Paths.get(path)).asScala.toList + val jsonText = lines.mkString(System.lineSeparator()) + val item2 = example.deserialize(jsonText) + println(item2) + Files.write(Paths.get(out), item2.toString().getBytes(StandardCharsets.UTF_8)) + } + } + + } +}