I have developed an F# REST service framework which has recently gone into production usage, and one of the follow-up tools I'm working on is an automatic OpenAPI spec generator. The generator uses the FSharp.Compiler.Services library to examine each function exposed as a service entry point and generate the API spec, including schemas for each parameter. These parameters are often single-case union types which use a special rules computation expression to define validation rules, like the following:
type AccoutNubmerValidationError =
| AccountNumberMustBeTenDigitsLong of string
| AccountNumberMustBeNumerical of string
/// Account Number must be a 10-digit numerical string
[<Struct; Validated>] type AccoutNumber = private AccountNumber of string
module AccountNumber =
let create : string -> Result<AccountNumber, AccountNumberValidationError> =
rules {
rule (Rules.length 10) AccountNumberBeTenDigitsLong
rule (Rules.pattern "^[\d]{10}$") AccountNumberMustBeNumerical
}
I would like to parse each rule in the computation expression (which is a CustomOperation on the RulesBuilder class) so I can generate the correct values for length, pattern, etc. on the OpenApi schema for AccountNumber. While I can identify the correct binding and I have the FSharpExpr for the body, I haven't figured out exactly what I would need to do to parse that expression and extract the values for each rule.
If there is any documentation available on how to parse the bodies of computation expressions using F# Compiler Services, I haven't yet been able to find it. Does anyone have any experience they can share to help me figure it out?
EDIT
I received a close vote for 'asking for a tutorial', so I want to clarify my problem. I have an instance of an FSharpMemberOrFunctionOrValue for the create binding in the example above, as well as an FSharpExpr for the body of the expression. The expression looks something like this (represented as JSON to make it readable):
{
"Type": "FSharpImplementationFileDeclaration.MemberOrFunctionOrValue",
"Range": {
"StartLine": 15,
"StartCol": 4,
"EndLine": 15,
"EndCol": 9
},
"Properties": [
[
"v",
"val create"
],
[
"vs",
[]
]
],
"Childs": [
{
"Type": "BasicPatterns.Application",
"Range": {
"StartLine": 15,
"StartCol": 4,
"EndLine": 15,
"EndCol": 9
},
"Properties": [],
"Childs": [
{
"Type": "BasicPatterns.Lambda",
"Range": {
"StartLine": 15,
"StartCol": 4,
"EndLine": 15,
"EndCol": 9
},
"Properties": [
[
"lambdaVar",
"val builder#"
]
],
"Childs": [
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 17,
"StartCol": 8,
"EndLine": 17,
"EndCol": 64
},
"Properties": [
[
"memberOrFunc",
"member Require"
],
[
"typeArg2",
"type Microsoft.FSharp.Core.string"
]
],
"Childs": [
{
"Type": "BasicPatterns.Value",
"Range": {
"StartLine": 17,
"StartCol": 8,
"EndLine": 17,
"EndCol": 64
},
"Properties": [
[
"valueToGet",
"val builder#"
]
],
"Childs": []
},
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 16,
"StartCol": 8,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"memberOrFunc",
"member Require"
],
[
"typeArg2",
"type Microsoft.FSharp.Core.string"
]
],
"Childs": [
{
"Type": "BasicPatterns.Value",
"Range": {
"StartLine": 16,
"StartCol": 8,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"valueToGet",
"val builder#"
]
],
"Childs": []
},
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 16,
"StartCol": 8,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"memberOrFunc",
"member Yield"
],
[
"typeArg2",
"type Microsoft.FSharp.Core.obj * Microsoft.FSharp.Core.string"
]
],
"Childs": [
{
"Type": "BasicPatterns.Value",
"Range": {
"StartLine": 16,
"StartCol": 8,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"valueToGet",
"val builder#"
]
],
"Childs": []
},
{
"Type": "BasicPatterns.Const",
"Range": {
"StartLine": 16,
"StartCol": 8,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"constType",
"type Microsoft.FSharp.Core.unit"
],
[
"constValueObj",
null
]
],
"Childs": []
}
]
},
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 16,
"StartCol": 17,
"EndLine": 16,
"EndCol": 32
},
"Properties": [
[
"memberOrFunc",
"val raise"
],
[
"typeArg2",
"type Microsoft.FSharp.Core.obj"
]
],
"Childs": [
{
"Type": "BasicPatterns.Const",
"Range": {
"StartLine": 16,
"StartCol": 17,
"EndLine": 16,
"EndCol": 32
},
"Properties": [
[
"constType",
"type Microsoft.FSharp.Core.int32"
],
[
"constValueObj",
1
]
],
"Childs": []
}
]
},
{
"Type": "BasicPatterns.Const",
"Range": {
"StartLine": 16,
"StartCol": 34,
"EndLine": 16,
"EndCol": 63
},
"Properties": [
[
"constType",
"type Microsoft.FSharp.Core.string"
],
[
"constValueObj",
"Must be 10 digits in length"
]
],
"Childs": []
}
]
},
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 17,
"StartCol": 17,
"EndLine": 17,
"EndCol": 43
},
"Properties": [
[
"memberOrFunc",
"val raise"
],
[
"typeArg2",
"type Microsoft.FSharp.Core.obj"
]
],
"Childs": [
{
"Type": "BasicPatterns.Const",
"Range": {
"StartLine": 17,
"StartCol": 17,
"EndLine": 17,
"EndCol": 43
},
"Properties": [
[
"constType",
"type Microsoft.FSharp.Core.int32"
],
[
"constValueObj",
1
]
],
"Childs": []
}
]
},
{
"Type": "BasicPatterns.Const",
"Range": {
"StartLine": 17,
"StartCol": 45,
"EndLine": 17,
"EndCol": 64
},
"Properties": [
[
"constType",
"type Microsoft.FSharp.Core.string"
],
[
"constValueObj",
"Must be numerical"
]
],
"Childs": []
}
]
}
]
},
{
"Type": "BasicPatterns.Call",
"Range": {
"StartLine": 15,
"StartCol": 4,
"EndLine": 15,
"EndCol": 9
},
"Properties": [
[
"memberOrFunc",
"val rules"
]
],
"Childs": []
}
]
}
]
}
The problem is, I don't even see a reference to Rules.length or Rules.pattern in this expression tree. Presumably, this must be because I'm creating some intermediate partially-applied function that's represented elsewhere in the expression tree for the program. Is there a deterministic way to identify which portions of the expression tree I need to navigate and then find the actual constant values used in the partially-applied functions so that I can reliably use them programmatically?
Related
I am trying to get the weight for every bone using a gltf json file but i dont know how
here is the json file i am trying to get the weight from:
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 317,
"max": [
10.570027351379395,
15.918471336364746,
6.125584125518799
],
"min": [
-2.611180305480957,
-1.9711413383483887,
-7.055622577667236
],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 317,
"type": "VEC3"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 317,
"type": "VEC2"
},
{
"bufferView": 3,
"componentType": 5121,
"count": 317,
"type": "VEC4"
},
{
"bufferView": 4,
"componentType": 5126,
"count": 317,
"type": "VEC4"
},
{
"bufferView": 5,
"componentType": 5123,
"count": 768,
"type": "SCALAR"
},
{
"bufferView": 6,
"componentType": 5126,
"count": 5,
"type": "MAT4"
}
],
"asset": {
"generator": "Khronos glTF Blender I/O v1.5.17",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 3804,
"byteOffset": 0
},
{
"buffer": 0,
"byteLength": 3804,
"byteOffset": 3804
},
{
"buffer": 0,
"byteLength": 2536,
"byteOffset": 7608
},
{
"buffer": 0,
"byteLength": 1268,
"byteOffset": 10144
},
{
"buffer": 0,
"byteLength": 5072,
"byteOffset": 11412
},
{
"buffer": 0,
"byteLength": 1536,
"byteOffset": 16484
},
{
"buffer": 0,
"byteLength": 320,
"byteOffset": 18020
}
],
"buffers": [
{
"byteLength": 18340,
"uri": "manoe.bin"
}
],
"images": [
{
"mimeType": "image/png",
"name": "Material_002_baseColor",
"uri": "Material_002_baseColor.png"
}
],
"materials": [
{
"doubleSided": true,
"name": "Material.001",
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0,
"roughnessFactor": 0.4000000059604645
}
}
],
"meshes": [
{
"name": "Cone.001",
"primitives": [
{
"attributes": {
"JOINTS_0": 3,
"NORMAL": 1,
"POSITION": 0,
"TEXCOORD_0": 2,
"WEIGHTS_0": 4
},
"indices": 5,
"material": 0
}
]
}
],
"nodes": [
{
"name": "Bone.004",
"rotation": [
0.17750653624534607,
-2.8648443617385055e-8,
-0.9102426171302795,
0.37409862875938416
],
"scale": [
1.000000238418579,
1,
1
],
"translation": [
-4.411018750261064e-8,
0.8281745314598083,
1.4894996525072202e-7
]
},
{
"children": [
0
],
"name": "Bone.002",
"rotation": [
0.2184765785932541,
-0.2617807984352112,
-0.7560504078865051,
0.558682918548584
],
"scale": [
1.0000003576278687,
1,
1
],
"translation": [
6.093483762015239e-8,
0.8281747698783875,
-1.283853023892334e-9
]
},
{
"name": "Bone.003",
"rotation": [
-0.4916436970233917,
7.804754176277129e-8,
-0.14189021289348602,
0.8591586947441101
],
"scale": [
1,
1.0000001192092896,
0.9999999403953552
],
"translation": [
-2.4224684125329077e-7,
0.8281750679016113,
-2.68131650216219e-9
]
},
{
"children": [
1,
2
],
"name": "Bone.001",
"rotation": [
-0.11594496667385101,
0.08448944985866547,
-0.5036448836326599,
0.8519155383110046
],
"scale": [
1.000000238418579,
0.9999995827674866,
1
],
"translation": [
-1.357730283757519e-8,
1.0000007152557373,
-3.471412068391244e-14
]
},
{
"children": [
3
],
"name": "Bone",
"rotation": [
2.3709270635663415e-7,
2.8494374859633353e-8,
6.9593789722201e-15,
1
],
"scale": null,
"translation": null
},
{
"mesh": 0,
"name": "Cone.001",
"skin": 0
},
{
"children": [
5,
4
],
"name": "ArmatureBase",
"scale": [
5.400000095367432,
5.400000095367432,
5.400000095367432
]
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987
}
],
"scene": 0,
"scenes": [
{
"name": "Scene",
"nodes": [
6
]
}
],
"skins": [
{
"inverseBindMatrices": 6,
"joints": [
4,
3,
1,
0,
2
],
"name": "ArmatureBase"
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
]
}
The glTF overview card summarizes it nicely:
Every vertex can be associated with up to 4 joints, each with a given weight.
These are encoded in the components of the JOINTS_0 and WEIGHTS_0 accessors. In your case those are accessors 3 and 4 and bufferviews 3 and 4 with a component type of byte and float, respectively.
One more indirection into the bufferViews component here tells you where in the binary file to look.
I have this grammar:
let lexer = moo.compile({
comment: { match: /[\/\/.*?$|][^\n]+/, value: (s:string) => s.slice(1).trim() },
newline: { match: /[\n]+/, lineBreaks: true }
});
%}
#lexer lexer
main ->
element
| main %newline element
element -> comment
comment -> %comment
Now when I feed nearley the following input: //\n//\n//\n//\n// I get this result:
[
[
[
[
[
[
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 0,
"lineBreaks": 0,
"line": 1,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 2,
"lineBreaks": 1,
"line": 1,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 3,
"lineBreaks": 0,
"line": 2,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 5,
"lineBreaks": 1,
"line": 2,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 6,
"lineBreaks": 0,
"line": 3,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 8,
"lineBreaks": 1,
"line": 3,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 9,
"lineBreaks": 0,
"line": 4,
"col": 1
}
]
]
],
{
"type": "newline",
"value": "\n",
"text": "\n",
"offset": 11,
"lineBreaks": 1,
"line": 4,
"col": 3
},
[
[
{
"type": "comment",
"value": "/",
"text": "//",
"offset": 12,
"lineBreaks": 0,
"line": 5,
"col": 1
}
]
]
]
]
I dont quite understand why the resulting array is so deeply nested and if theres a way to just have it flat for each elements. Like comments on the same semantic level should be part of one array and not nested.
Okay, so it turns out you have to pass a post-processor to each rule if you don't want them nested in arrays.
For instance like this:
main ->
element {% d => ({ type: "main_element", data: d[0]}) %}
| main %newline element {% d => ({ type: "main_element", data: d[2], main_data: d[0]}) %}
element -> %comment
{% d => ({ type: "element", data: d[0]}) %}
This will result in a flat structure as expected:
[
{
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 12,
"lineBreaks": 0,
"line": 5,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 9,
"lineBreaks": 0,
"line": 4,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 6,
"lineBreaks": 0,
"line": 3,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 3,
"lineBreaks": 0,
"line": 2,
"col": 1
}
},
"main_data": {
"type": "main_element",
"data": {
"type": "element",
"data": {
"type": "comment",
"value": "/",
"text": "//",
"offset": 0,
"lineBreaks": 0,
"line": 1,
"col": 1
}
}
}
}
}
}
}
]
What's the best way to sum all the "amounts" of all "expenses", for each user?
I've tried a few different things but I can't quite get it right. It should return 2 values: 20.0 and 24.90
[
{
"id": 3,
"company": {
"id": 2
},
"user": {
"id": 3,
"first_name": "Fred",
"last_name": "Smith",
"email": "asdfasf",
"is_suspended": false,
"vendor_id": "FS-100",
"username": "etytyurtyu",
"expense": [
{
"id": 7,
"date": "2019-12-14T00:00:00.000Z",
"amount": 20.0,
"payment_type": "companyAccount",
"last_modified": "2019-12-16T23:50:00.459064Z",
"receipt_uri": [
"URL8",
"URL9"
],
"user": {
"id": 3
},
"expense_type": {
"id": 4
},
"booking": {
"id": "HI-3565346"
}
}
]
}
},
{
"id": 2,
"company": {
"id": 2
},
"user": {
"id": 2,
"first_name": "Pierre",
"last_name": "XXXMar",
"email": "asdfasdfads",
"is_suspended": false,
"vendor_id": "PM-100",
"username": "asdfas",
"expense": [
{
"id": 2,
"date": "2019-12-16T00:00:00.000Z",
"amount": 12.45,
"payment_type": "provided",
"last_modified": "2019-12-16T19:01:37.092932Z",
"receipt_uri": [
"URL1"
],
"user": {
"id": 2
},
"expense_type": {
"id": 6
},
"booking": {
"id": "MU-123414"
}
},
{
"id": 5,
"date": "2019-12-08T00:00:00.000Z",
"amount": 12.45,
"payment_type": "provided",
"last_modified": "2019-12-16T23:50:00.459064Z",
"receipt_uri": [
"URL1"
],
"user": {
"id": 2
},
"expense_type": {
"id": 6
},
"booking": {
"id": "MU-123414"
}
},
{
"id": 3,
"date": "2019-12-17T00:00:00.000Z",
"amount": 20.0,
"payment_type": "companyCard",
"last_modified": "2019-12-16T19:01:37.092932Z",
"receipt_uri": [
"URL5",
"URL6"
],
"user": {
"id": 2
},
"expense_type": {
"id": 12
},
"booking": {
"id": "HI-3565346"
}
}
]
}
}
]
Thanks
You would first map the expenses amounts, then fold it into a single value, like this :
double sum = expenses.map((expense) => expense.amount).fold(0, (prev, amount) => prev + amount);
List item
For the sake of completeness: In 2021 you can replace the .fold(0, (prev, amount) => prev + amount) part by just calling .sum on the list. Just import import 'package:collection/collection.dart'; first –
tmaihoff
I try to use multiple buffers to include different bin files(one for geometry data, one for animation data) but the animation bin file in buffers1 does not work in Babylonjs [Uncaught TypeError: Cannot read property 'frame' of undefined].
My questions are: Is there something wrong in my gltf file? How to use multiple bin file in gltf?
Below is my complete gltf file:
And the bin files
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 24,
"type": "VEC3",
"max": [
0.3,
0.3,
0.3
],
"min": [
-0.3,
-0.3,
-0.3
],
"name": "Positions Accessor"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 24,
"type": "VEC3",
"name": "Normals Accessor"
},
{
"bufferView": 2,
"componentType": 5126,
"count": 24,
"type": "VEC2",
"name": "UV Accessor 0"
},
{
"bufferView": 3,
"componentType": 5125,
"count": 36,
"type": "SCALAR",
"name": "Indices Accessor"
},
{
"bufferView": 4,
"componentType": 5126,
"count": 3,
"type": "SCALAR",
"max": [
2.0
],
"min": [
0.0
],
"name": "Animation Sampler Input"
},
{
"bufferView": 5,
"componentType": 5126,
"count": 3,
"type": "VEC3",
"name": "Animation Sampler Output"
}
],
"animations": [
{
"channels": [
{
"sampler": 0,
"target": {
"node": 0,
"path": "translation"
}
}
],
"samplers": [
{
"input": 4,
"output": 5
}
]
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Test_Geometry.bin",
"byteLength": 912
},
{
"uri": "Test_Animation.bin",
"byteLength": 48
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 288,
"name": "Positions"
},
{
"buffer": 0,
"byteOffset": 288,
"byteLength": 288,
"name": "Normals"
},
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 192,
"name": "Texture Coords 0"
},
{
"buffer": 0,
"byteOffset": 768,
"byteLength": 144,
"name": "Indices"
},
{
"buffer": 1,
"byteLength": 12,
"name": "Animation Sampler Input"
},
{
"buffer": 1,
"byteOffset": 12,
"byteLength": 36,
"name": "Animation Sampler Output"
}
],
"images": [
{
"uri": "Test/MultipleBuffers.png"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
}
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"indices": 3,
"material": 0
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"textures": [
{
"source": 0
}
]
}
New to the notion of Rails mapping, and would love to get some help on this from the stack overflow community.
Currently I have this JSON response from the Embedly API and don't really know how to map it to my very simple 'posts' model. I'm trying to get the image/title/url from the JSON response and map it into my database in the relevant fields, so in the JSON response below these would be 'original_url', 'image' and 'title'.
{
"provider_url": "http://piccsy.com",
"authors": [],
"provider_display": "piccsy.com",
"related": [],
"favicon_url": "http://piccsy.com/favicon.ico",
"keywords": [],
"app_links": [],
"original_url": "http://piccsy.com/2015/02/rihanna-sharks-harpers-bazaar-march-2015-photoshoot3",
"media": {},
"content": null,
"entities": [],
"provider_name": "Piccsy",
"type": "html",
"description": "Beautiful, inspirational and creative images from Piccsy. Thousands of Piccs from all our streams, for you to browse, enjoy and share with a friend.",
"embeds": [],
"images": [
{
"width": 728,
"url": "http://img2.piccsy.com/cache/images/56/8f/bed__396d8_cecf850824130_99f-post.jpg",
"height": 1092,
"caption": null,
"colors": [
{
"color": [
190,
211,
212
],
"weight": 0.3095703125
},
{
"color": [
114,
159,
171
],
"weight": 0.247314453125
},
{
"color": [
0,
52,
68
],
"weight": 0.244140625
},
{
"color": [
25,
99,
117
],
"weight": 0.198974609375
}
],
"entropy": 5.94797179868,
"size": 318918
},
{
"width": 200,
"url": "http://piccsy.com/piccsy/images/layout/logo/e02f43.200x200.jpg",
"height": 200,
"caption": null,
"colors": [
{
"color": [
215,
51,
67
],
"weight": 0.701904296875
},
{
"color": [
250,
252,
252
],
"weight": 0.298095703125
}
],
"entropy": 0.686638083774,
"size": 18691
}
],
"safe": true,
"offset": null,
"cache_age": 86065,
"lead": null,
"language": null,
"url": "http://piccsy.com/2015/02/rihanna-sharks-harpers-bazaar-march-2015-photoshoot3",
"title": "Rihanna-sharks-harpers-bazaar-march-2015-photoshoot3",
"favicon_colors": [
{
"color": [
208,
37,
38
],
"weight": 0.000244140625
},
{
"color": [
0,
0,
0
],
"weight": 0.000244140625
}
],
"published": null
}
My posts model contains very simple name, url and image fields which are all accepting strings. Any help on mapping this to a model would be brilliant, up till now I've only done very simple JSON responses and this one is a bit out of my league.
Thanks for your time.
You can just parse json response, and get required fields from it
json_response = '{your json response from api}'
response_hash = JSON.parse(json_response)
MyModel.create!(url: response_hash[:original_url], title: response_hash[:title])
But there is problem with images, response contains multiple images, so probably you should have ImageModel that belongs to MyModel, and MyModel has_many ImageModels.
Then you can do:
model = MyModel.create!(url: response_hash[:original_url], title: response_hash[:title])
response_hash[:images].each do |image|
model.images.create!(url: image[:url])
end