v icon indicating copy to clipboard operation
v copied to clipboard

scoped attribures

Open Eun opened this issue 1 year ago • 13 comments

Describe the feature

Structs and Structfields support having attributes, as an example:

struct User {
    id int               [skip]
    name string          [required]
    created_on time.Time [skip]
}

It should be required to scope these attributes to the package that defines the behavior and acts on the attribute value.

Example:

[orm.table: 'users']
struct User {
    id int               [orm.primary; orm.sql: serial; json.skip]
    name string          [orm.required; json.required]
    created_on time.Time [required; json.skip]
}

Note that prefixing is just the simplest variant, other syntactic options could fit better.

Use Case

A simple use case would be using the same struct for orm and json.

Lets say we want to build a simple rest api with CRUD operations, the data should be kept in a database.

We can define a struct the following:

[table: 'users']
struct User {
    id int               [primary; sql: serial]
    name string          [required]
    created_on time.Time [required]
}

Now that would suit perfect the database, however all json decode operations would require custom logic to ignore the id and created_on fields passed in by the client.

e.g. the create operation:

fn (mut app App) create() vweb.Result {
    user = json.decode(User, app.req.data) or {
        app.set_status(400, '')
        return app.json({
            'error': 'Failed to decode json, error: $err'
        })
    }
    // we do not allow the clients to specify id or created_on
    user.id = 0
    user.created_on = time.utc()
    ...
}

Proposed Solution

Instead of having "global" tags, like skip or required, it should be mandatory to scope the tag to the handling package:

[orm.table: 'users']
struct User {
    id int               [orm.primary; orm.sql: serial; json.skip]
    name string          [orm.required; json.required]
    created_on time.Time [orm.required; json.skip]
}

Other Information

An alternative would be that developers create two structs, one for the "wire", and one for the database.

That has these two (major) negatives:

  1. Data has to be continuously copied back and forth.
  2. Maintenance: an attribute that was added in the database type does not land on the wire by default

Acknowledgements

  • [x] I may be able to implement this feature request
  • [X] This feature might incur a breaking change

Version used

v0.3.3

Environment details (OS name and version, etc.)

Processor: 10 cpus, 64bit, little endian, Apple M1 Max
CC version: Apple clang version 14.0.0 (clang-1400.0.29.202)

getwd: /Users/Tobias
vmodules: /Users/Tobias/.vmodules
vroot: /Users/Tobias/vlang/v0.3.3
vexe: /Users/Tobias/vlang/v0.3.3/v
vexe mtime: 2023-01-30 17:54:17
is vroot writable: true
is vmodules writable: true
V full version: V 0.3.3 d1f57ea

Git version: git version 2.39.2
Git vroot status: Error: fatal: not a git repository (or any of the parent directories): .git
.git/config present: false
thirdparty/tcc status: thirdparty-macos-amd64 46662e20

Eun avatar Mar 27 '23 13:03 Eun

I 100% agree.

medvednikov avatar Mar 29 '23 12:03 medvednikov

But I'd keep the current syntax: [json:required]

edit

hm that would actually conflict with [json:field_name] but we can force using quotes for those: [json:'field_name'].

medvednikov avatar Mar 29 '23 12:03 medvednikov

Quotes are also good, so it would look like this? [json:'field_name', required] How about skipping? should we also use golangs format: [json:-] or rather this: [json:skip]

Eun avatar Mar 29 '23 13:03 Eun

Instead of having a system that can break and go unnoticed with a simple typo, we could structure attributes to make them make more sense and be more robust, like in Java, C#, Kotlin, etc.

Here is an example of how it could look like:

// in json/json.v

// Bodyless attribute 
attribute Skip

// Attribute with value
attribute SerialName(string)
// in main.v
import json


struct Foo {
    bar int    [json.SerialName('Bar')]
    baz string [json.Skip]
}

RGBCube avatar Mar 29 '23 13:03 RGBCube

Instead of having a system that can break and go unnoticed with a simple typo, we could structure attributes to make them make more sense and be more robust, like in Java, C#, Kotlin, etc.

Here is an example of how it could look like:

// in json/json.v

// Bodyless attribute 
attribute Skip

// Attribute with value
attribute SerialName(string)
// in main.v
import json


struct Foo {
    bar int    [json.SerialName('Bar')]
    baz string [json.Skip]
}

at the end of the day, attribute would be a struct?(ref. to gen)

and how would we get the arguments?

Ddiidev avatar Mar 29 '23 13:03 Ddiidev

yep I think making attribute a struct would be good:

// in json.v
attribute SerialName {
    name string
}
attribute Skip {}
// in main.v
struct Foo {
    bar int    [json.SerialName{'Bar'}]
    baz string [json.Skip]
}

or maybe better:

attribute is a reserved struct

// in json.v
attribute {
    name string
    skip bool
    required bool
}
// in main.v
struct Foo {
    bar int    [json{name:'Bar'}]
    baz string [json{skip:true}]
}

Eun avatar Mar 29 '23 13:03 Eun

attribute My_attribute(string, int, f32)

well this could be abstracted to a struct, eg...

struct Foo {
     bar int [My_attribute("test", 1, 1.3)]
     baz string
}

V would build a struct that way...

struct My_attribute {
pub:
	value1 string
	value2 int
	value3 f32
}
fn (f Foo) test() {
	$for t_ in Test.attributes {
		t_ is My_attribute {
			....
		}
	}
}

Ddiidev avatar Mar 29 '23 13:03 Ddiidev

yep I think making attribute a struct would be good:

// in json.v
attribute SerialName {
    name string
}
attribute Skip {}
// in main.v
struct Foo {
    bar int    [json.SerialName{'Bar'}]
    baz string [json.Skip]
}

or maybe better: attribute is a reserved struct

// in json.v
attribute {
    name string
    skip bool
    required bool
}
// in main.v
struct Foo {
    bar int    [json{name:'Bar'}]
    baz string [json{skip:true}]
}

it seems simpler

Ddiidev avatar Mar 29 '23 13:03 Ddiidev

How about [attribute] attribute?

[attribute]
struct SerialName {
    name string
}
[attribute]
struct Skip {}

it doesn't add special syntax

lemoncmd avatar Mar 29 '23 15:03 lemoncmd

How about [attribute] attribute?

[attribute]
struct SerialName {
    name string
}
[attribute]
struct Skip {}

it doesn't add special syntax

I suggested this in the discord server, and i agree. It would be much simpler, but a little verbose for bigger attributes, which won't be common anyway.

One big question i have is how will empty attributes be used? [json.Skip] or [json.Skip{}] ?

edit: the attribute attribute would also be the only allowed non struct attribute, also.

RGBCube avatar Mar 29 '23 15:03 RGBCube

Its a weak opinion but I would rather vote for one struct defining all attributes than multiple structs defining multiple attributes, the reason is readability and maintainability. The reader of the source code has just to find this one struct instead of finding multiple scattered around the source code.

I don't care if it is like

attributes {
    name string
    skip bool
    required bool
}

or

[attributes]
struct Attributes {
    name string
    skip bool
    required bool
}

Eun avatar Mar 29 '23 15:03 Eun

yes, but what about the builtiin attributes, will they remain as they are in a form of use reserved for them?

Ddiidev avatar Mar 29 '23 15:03 Ddiidev

yes, but what about the builtiin attributes, will they remain as they are in a form of use reserved for them?

Could be like normal attributes:

[MarkUsed]
fn foo() {}

RGBCube avatar Apr 02 '23 12:04 RGBCube