cue-lang / cue

The home of the CUE language! Validate and define text-based and dynamic configuration
https://cuelang.org
Apache License 2.0
5.14k stars 294 forks source link

Service support? #69

Open cueckoo opened 3 years ago

cueckoo commented 3 years ago

Originally opened by @matthewmueller in https://github.com/cuelang/cue/issues/69

I'm very interested in CUE for inter process communication. I was wondering if there are any plans to add function signatures to enforce how two processes can communicate with each other?

Something like services in Thrift / Protobuf or queries & mutations in a GraphQL schema.

This seems a bit outside of the core motivation for CUE, but it does seem like CUE would fit very well here too.

cueckoo commented 3 years ago

Original reply by @jlongtine in https://github.com/cuelang/cue/issues/69#issuecomment-516921341

@matthewmueller I think some work has been done on Protobuf: https://github.com/cuelang/cue/tree/master/encoding/protobuf

@mpvl would have a better idea of the state of that code, though.

cueckoo commented 3 years ago

Original reply by @mpvl in https://github.com/cuelang/cue/issues/69#issuecomment-517227475

@matthewmueller This is not planned at the moment, although I have given it some thought. The main reason not though is that I didn't have a concrete use case and I'm not sure how that would look like.

But as @jlongtine said, the infrastructure for Proto parsing and conversion is already in place. So it would be useful to know what exactly expect to do with such a feature.

cueckoo commented 3 years ago

Original reply by @mpvl in https://github.com/cuelang/cue/issues/69#issuecomment-533706195

@matthewmueller if you have a concrete example of how you would want to be using CUE this way I would be quite interested.

cueckoo commented 3 years ago

Original reply by @matthewmueller in https://github.com/cuelang/cue/issues/69#issuecomment-533729137

Hey @mpvl! I'm imagining an implementation of protobuf's generators that adds constraint-based validation.

Tons of unknowns here, but conceptually I was thinking something like this:

user.cue

email: "\w+@\w+\.\w{2,}" // some regex for email

CreateInput: {
    email: email
    password: string // perhaps @len(string) > 0 <= 30
    born: >= 1900 <= 2019
}

CreateOutput: {
    id: >=0
    email: email
    born: >= 1900 <= 2019
}

UpdateInput: {
    email?: email
    password?: string
    born?: >= 1900 <= 2019
}

UpdateOutput: {
    ok: true | false
}

User: {
  create(in: CreateInput): CreateOutput
  update(in: UpdateInput): UpdateOutput
}

Then, run cue generate user.cue --out cue/user.go. This will create cue/user.go. This file will contain a User struct (or maybe just an interface?) and a Marshal and an Unmarshal. The Marshal and Unmarshal bake in constraint-based validation.

Usage would look something like this:

main.go

package main

import "cue/user"

func main() {
  buf, err := ioutil.ReadAll(os.Stdin)
  if err != nil {
    panic(err)
  }
  var u user.CreateInput
  if err := user.Create.Unmarshal(buf, &u); err != nil {
    panic(err)
  }
  fmt.Println("got a user.CreateInput!", u)
}

@neelance do you have some thoughts on this? Our discussion sparked this initial issue 😊

cueckoo commented 3 years ago

Original reply by @mpvl in https://github.com/cuelang/cue/issues/69#issuecomment-739228231

Just to add some thoughts here: with the upcoming proposal to generalize and harmonize the syntax, something that gets close to the above rpc signatures is:

create: {in: CreateInput}: CreateOutput // POSSIBLE NEW NOTATION FOR RPC DEFINITIONS

or maybe

create: {in: CreateInput} :: CreateOutput

(The scanner still recognizes ::, even though it is unused)

Especially the former would require a minimal syntactic adjustment and generalizes, syntactically, what expressions are allowed as a label.

We are also considering allowing the call syntax for structs. Consider, this,

foo: {a: int, b: int, out: a + b } // struct with embedded scalar

call: (foo&{a: 1, b: 2}).out        // 3

On tip, which supports embedded scalars, this can be written as:

foo: {#a: int, #b: int, #a + #b } // struct with embedded scalar

call: foo&{_, #a: 1, #b: 2}         // 3

The ideas is to allow an extension of the builtin syntax for structs as well, allowing

foo: { #a: int, #b: int, #a + #b } // struct with embedded scalar

call: foo(#a: 1, #b: 2) // 3

Where foo(#a: 1, #b: 2) is a shorthand for foo&{#a: 1, #b: 2}, with the additional requirement that #a and #b MUST be defined in foo, even if foo is an open struct.

This would also allow builtins to have Swift-style named arguments, which would allow some things to be written clearly, like:

range(from: 1, to: <5, by: 1)

Conversely, we could consider unnamed arguments for structs to map to the fields with name #0, #1, etc. Note that these are illegal identifiers right now to allow introducing this without causing backwards incompatibility.

These extensions to the call syntax also suggest the following possible syntax for rpc calls:

create: (in: CreateInput): CreateOutput

The problem with this is that there is an ambiguity with the proposed syntax (#165) for computed labels, especially if we want to allow unnamed arguments. A possible solution for this is to repurpose :::

create: (in: CreateInput) :: CreateOutput  // ALTERNATIVE NEW NOTATION FOR RPC DEFINITIONS

This would remove that ambiguity and this notation has precedence in functional languages. A disadvantage of this approach is that it introduces more syntax (although limited). An advantage of doing so, though, is that it also would allow named return arguments without complications:

create: (in: CreateInput) :: (out: CreateOutput)

Another advantage of using :: here is also that it makes it clearer that this is not defining data.

Note that with both proposed syntaxes, the existing field syntax would allow attributes to be defined along the arguments. This means it allows attributing additional meaning to arguments that are irrelevant to CUE, such as:

create: (in: CreateInput @grpc(stream)) :: (out: CreateOutput @grpc(stream)) 

or

create: {in: CreateInput @grpc(stream)}: {out: CreateOutput @grpc(stream)}

So so far, the best candidates are either of the form {}: {} or () :: (), where in both cases the return value can be a normal expression. The {}: {} form will require less additional syntax, but may be too indistinguishable from normal data. The () :: () form requires more additional syntax, but stands out more and also allows writing unnamed signatures like int :: int.

Thoughts welcome.

cueckoo commented 3 years ago

Original reply by @verdverm in https://github.com/cuelang/cue/issues/69#issuecomment-739412692

I'm in favor of the () :: () syntax for its visual standout properties. This will help with readability, especially when I revisit code after a long time away, and likely easier for new users to understand what is going on. The call syntax as it is today can be confusing at first.