Skip to content
This repository has been archived by the owner on Nov 18, 2021. It is now read-only.

Support type assertion #881

Closed
morlay opened this issue Apr 6, 2021 · 11 comments
Closed

Support type assertion #881

morlay opened this issue Apr 6, 2021 · 11 comments
Labels
FeatureRequest New feature or request

Comments

@morlay
Copy link

morlay commented Apr 6, 2021

Is your feature request related to a problem? Please describe.

Based on cuelang, i wrote a small tool to manage k8s resources similar like helm.
Avoiding forking cuelang to add built-in functions, i use an attr @translate("xxx") to convert some cue value to special configuration format which cuelang not provided.

https://github.com/octohelm/cuemod#post-processing-translatename

For example, a toml format:

configMaps: [Name=_]: v1.#ConfigMap & {
  apiVersion: "v1"
  kind: "ConfigMap"
  metadata: name: Name
} 

// data is a [string]: string
configMaps: "some-config": data: "xxx.toml": json.Marshal({ a: 1 }) @translate("toml")

Now I have to wrap with json.Marshal to make type constraints happy, then convert to toml from json raw codes.

But if type assertion supported. we could avoid wrap the json.Marshal, and convert to toml from struct value directly.

Describe the solution you'd like

may just a new built-in value attr @as(type)

configMaps: "some-config": data: "xxx.toml": { a: 1 } @as(string) @translate("toml")

Describe alternatives you've considered

or golang like .(type)

configMaps: "some-config": data: "xxx.toml": { a: 1 }.(string) @translate("toml")
@morlay morlay added the FeatureRequest New feature or request label Apr 6, 2021
@verdverm
Copy link
Contributor

verdverm commented Apr 6, 2021

Now I have to wrap with json.Marshal to make type constraints happy, then convert to toml from json raw codes.

Have you explored cue.Value.Validate and the cue.Options? I believe these should provide this

Also, instead of this function, you could consider using cue.Value.Decode

https://github.com/octohelm/cuemod/blob/cc82c7751c805812feae69f4853b0ab13bac2c76/pkg/translator/toml/toml.go#L45

@morlay
Copy link
Author

morlay commented Apr 6, 2021

Have you explored cue.Value.Validate and the cue.Options?

it call Validate() with Final() for each eval https://github.com/octohelm/cuemod/blob/main/pkg/cuemod/eval.go#L28
I haven't find a way to ignore validating for a special node.

Also, instead of this function, you could consider using Decode

When unmarshal json raw to map[string]interface{} (cue.Value.Decode is same).
number will be as float64.

In toml, float and int should be strict, this function here to fix this.

@verdverm
Copy link
Contributor

verdverm commented Apr 6, 2021

Validate takes Options, in which you can omit final

@morlay
Copy link
Author

morlay commented Apr 6, 2021

@verdverm But i need it do validation for each eval.

@verdverm
Copy link
Contributor

verdverm commented Apr 6, 2021

are you fetching mods during eval(s) and/or build(s)?

regarding the original question, what would @as(string) output? What if the value is a reference? What if that value is incomplete?

@morlay
Copy link
Author

morlay commented Apr 6, 2021

are you fetching mods during eval(s) and/or build(s)?

fetching mod only mod not exists in cache, but always checking during evals and builds.

regarding the original question, what would @as(string) output? What if the value is a reference? What if that value is incomplete?

no effects for output.

It is a validation enhancement.

When a value annotated @as(string), should validate the value as a string value (inspired by TypeScript).

for reference or incomplete value, should validate sub node first.
When sub node complete, them check the value annotated @as(string) be a string.

However, i feel golang like v.(type) may be better. Like golang, could let cue support type check conditions too.
(should be useful for or types like string | int)

let vv, isString = v.(string)
if isString {}

let vv, isInt = v.(int)
if isInt {}

@myitcv
Copy link
Contributor

myitcv commented Apr 7, 2021

I'm slightly confused as to what is being discussed/requested here.

It's already possible to check the type of a value:

package x

x: int | string

if (x & string) != _|_ {
	y: "string"
}
if (x & int) != _|_ {
	y: "int"
}

(although I will note this is one of the many confusing overloads for _|_ that will be addressed in an upcoming proposal for builtins, which might even look to drop _|_ from the language).

Is there anything else required here?

convert some cue value to special configuration format which cuelang not provided.

@morlay we're definitely open to expanding builtins beyond what is currently provided. There is clearly a balance between the standard builtins becoming a kitchen sink of everything, and us fragmenting the language by supporting custom builtins. Please feel free to make suggestions for things you think should be added: it will help to shape the conversation if we have real examples.

@morlay
Copy link
Author

morlay commented Apr 7, 2021

It's already possible to check the type of a value

@myitcv Cool. I learned one new tip.
seems I could check optional field in this way.

#X: {
	optional?: string
}

list: [...#X] & [
	{optional: "xxx"},
	{},
]

for i, x in list {
	if ({optional: _} & x) != _|_ {
		"\(i)": x.optional
	}
}

although I will note this is one of the many confusing overloads for | that will be addressed in an upcoming proposal for builtins, which might even look to drop | from the language

a built-in function complete(expr): bool or valid(expr): bool may be clear.

There is clearly a balance between the standard builtins becoming a kitchen sink of everything, and us fragmenting the language by supporting custom builtins.

I understand this, so I'm not requesting to add new built-in functions.
In downstream tools, I need to extends cuelang to speical usage.
For example, helm chart render supported (inspired by https://tanka.dev/helm)

After converting helm chart to cue codes, we could use helm chart like below:

package localpathprovisioner

import (
	"github.com/rancher/local-path-provisioner/deploy/chart"
)

"local-path-provisioner": {
        @translate("helm")
        
	chart
	
	release: name:      "local-path-provisioner"
	release: namespace: "local-path-provisioner"
	
	values: {}
} 

A realworld example: https://github.com/querycap/harbor/blob/master/components/harbor/app.cue

I don't think this feature should be a cuelang builtin.
so i just hope cuelang could provide way to create custom extensions easier.

Btw, cuelang is a hero.
With simple structure like https://github.com/octohelm/cuem/blob/main/release/release.cue,
I have already excaped from helm template successfully.

Back to this issue. I define a special attr @translate(method) to mark a struct node do spcial translating when final output. (https://github.com/octohelm/cuemod#toml)

if I mark like this

configMaps: "some-config": data: "xxx.toml": { a: 1 } @translate("toml") 

cue.Value.Validate will show a error, because the data field should be [string]: string

so I have to wrap json.Marshal to ensure the value is a string.

configMaps: "some-config": data: "xxx.toml": json.Marshal({ a: 1 }) @translate("toml") 

However, two disadvantages here:

  1. have to wrap json.Marshal everywhere
  2. converting to toml from json raw codes, will lost origin type metadatas (lost float or int, only know number).

@as(string) want to to force define custom validation for the special struct node.

configMaps: "some-config": data: "xxx.toml": { a: 1 } @as(string) @translate("toml") 

Also, cuelang api may provide cue.Value.Replace(cue.Value), which could resolve my problem too.

@mpvl
Copy link
Contributor

mpvl commented Apr 29, 2021

Also, cuelang api may provide cue.Value.Replace(cue.Value), which could resolve my problem too.

I'm still a bit unclear what the goal is here, but here are some other ideas:

An upcoming CL (planned for v0.4.0) allows aliases to field values. This makes it easier to adorn a field with conversions. So you could write, for instance:

configMaps: [string]: data: [string]: X={#json: json.Marshal(X) }

to inject a #json field alongside each configuration which you could then use for the export.

For example, in my client

import "encoding/json"

config: X={
	a: 1
	#json: json.Marshal(X)
}

resolves to

{
    config: {
        "foo"
        #json: "{\"a\":1}"
    }
}

Maybe that is something that is helpful.

@seh
Copy link

seh commented Apr 29, 2021

Where did the "foo" come from in the resolved text? Should that have been

a: 1

instead?

@cueckoo
Copy link

cueckoo commented Jul 3, 2021

This issue has been migrated to cue-lang/cue#881.

For more details about CUE's migration to a new home, please see cue-lang/cue#1078.

@cueckoo cueckoo closed this as completed Jul 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FeatureRequest New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants