Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the extension types, datetime and duration #35

Merged
merged 4 commits into from
Sep 19, 2024

Conversation

apg
Copy link
Collaborator

@apg apg commented Sep 16, 2024

See merged RFC 80.

This PR adds:

  • The datetime and duration type specified in RFC 80
  • Associated methods for those types
  • An implementation of operator overloading for ComparableValues, such as Long, Datetime, Duration.

This does not extend ComparableValue to Decimal, as there's no RFC / specification to make that so (doing so would be straightforward, however).

Both datetime and duration are backed by an int64 and can be lifted from a Go time.Time and a time.Duration respectively. It would be possible to remove the struct wrapper around the int64, as no other state is stored. We aren't doing this to prevent accidental casting / type assertions that remove the semantic meaning of these values.

(Note: there was some previous discussion in the strongdm fork of this repo: strongdm#3)

@apg apg marked this pull request as ready for review September 16, 2024 18:07
Copy link
Collaborator

@philhassey philhassey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope those help!

internal/eval/evalers.go Show resolved Hide resolved
if err != nil {
return nil, err
}
l, ok := v.(types.ComparableValue)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make ComparableValue be an interface that only exists within the evalers package? I don't think the general public needs to be aware of it.

if err != nil {
return zeroValue(), err
}
return types.Long(lhs.Value / 1000 / 60 / 60), nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can all theses things end up as constants? lhs.Value / Hour or something

year, month, day, hour, minute, second, milli int
i int
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Max nitpick - maybe less linebreaks would feel a bit nicer.


minute = 10*int(rune(s[i])-'0') + int(rune(s[i+1])-'0')

i = i + 2
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be nice if there was a few private constants
`
const dateOffset = 3
const hourOffset = 6

etc.

The changing of i, then using offsets from i is a bit hard to read.

return ok && a == b
}

func (a Datetime) Less(bi Value) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less and LessEqual should return errors in case the types are not the same.

Also, I'd call them LessThan and LessThanOrEqual to better match the cedar operators list:
https://docs.cedarpolicy.com/policies/syntax-operators.html

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively it could just take in the exact type only Less(bi Datetime)

Then you'd just do all the type checking work within the evalers package.

Could go either way on this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's less desirable for the method to take a concrete type given there's an interface here. I'll do a type assertion in the implementation.

// The following are not supported:
// "datetime(\"1970-01-01T00:00:00Z\")"
// {"fn":"datetime","arg":"1970-01-01T00:00:00Z"}
var res extValueJSON
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as this is in parity with what we did for Decimal/IPAddr it's okay for now. But maybe add an issue somewhere about the deficit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll just add it for duration/datetime, as they aren't hard to add.

"unicode"
)

const (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, since I mentioned those constants elsewhere, maybe they can all live in the internal/consts package?

}

// UnsafeDuration creates a duration via unsafe conversion from int, int64, float64
func UnsafeDuration[T int | int64 | float64](v T) Duration {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should multiply by 1000 or something? And the comment should indicate what it takes in, e.g. seconds?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably just drop this function and only include FromStdDuration.

@kjamieson-sdm kjamieson-sdm changed the title Add the extention types, datetime and duration Add the extension types, datetime and duration Sep 17, 2024
@apg apg force-pushed the apg/po-515-add-datetime branch 2 times, most recently from 948a208 to cf55f89 Compare September 17, 2024 22:52
@apg
Copy link
Collaborator Author

apg commented Sep 17, 2024

@philhassey I think all of your comments have been addressed.

type ComparableValue interface {
types.Value
LessThan(types.Value) (bool, error)
LessThanEqual(types.Value) (bool, error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change LessThanEqual to LessThanOrEqual, that better matches the cedar docs:

https://docs.cedarpolicy.com/policies/syntax-operators.html#function-lessThanOrEqual

return Duration{Value: negative * total}, nil
}

func (a Duration) Equal(bi Value) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All method and functions need a go-doc style docstring.

Value int64
}

func UnsafeDatetime[T int | int64 | float64](v T) Datetime {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this function isn't useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha! I think I stopped using it and didn't delete it. I'll get rid of it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently I didn't stop using it. 🤔 I will stop using it.

Copy link
Collaborator

@philhassey philhassey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope that helps!

types/value.go Show resolved Hide resolved
@philhassey
Copy link
Collaborator

I think this will be the next release, so maybe add in something to the README.md explaining the latest goodies in 0.3.2.

And also add some explanation as to how datetime is an accepted RFC, but it's a bit bleeding edge (as discussed in the meeting yesterday)

This PR does not add the duration type, nor the methods `toTime()`,
`offset()`, or `durationSince()` that would require it.

This PR adds operator overloading, by introducing a set of "Virtual"
comparison Evaler. Types that can overload operators must implement
the `Lesser` interface. Long and Datetime are implemented in this
PR. (Note: It is expected that there will be a Decimal operator
overload RFC at some later date, but that is not hooked into this)

The now obsolete Evalers for Long comparisons have not been
removed. A possible improvement to this PR would be to utilize
them when it is known that Longs are on the right and left side.

The datetime itself is backed by an int64, and can be lifted from
a Go `time.Time`, integer or float "unsafely."

Signed-off-by: Andrew Gwozdziewycz <[email protected]>
This builds on the Datetime PR.

It adds the duration type, and all of the associated methods that
require durations:

- datetime.toTime
- datetime.offset
- datetime.durationSince
- duration.toMilliseconds
- duration.toSeconds
- duration.toMinutes
- duration.toHours
- duration.toDays

Signed-off-by: Andrew Gwozdziewycz <[email protected]>
@apg apg force-pushed the apg/po-515-add-datetime branch 2 times, most recently from 4adb50c to 47eb643 Compare September 18, 2024 18:03
return Datetime{Value: t.UnixMilli()}, nil
}

// Equal returns true if the lfs represents the same timestamp as the rhs.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "lhs" instead of "lfs"

I feel like "lhs" and "rhs" is a bit odd. Maybe have it phrased "Equal returns true if the input represents the same timestamp."

Copy link
Collaborator

@philhassey philhassey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just a tiny text edit. Can @patjakdev give this a quick review as well?

@apg apg force-pushed the apg/po-515-add-datetime branch 2 times, most recently from 30daa0a to 78770a1 Compare September 18, 2024 22:02
@apg
Copy link
Collaborator Author

apg commented Sep 18, 2024

OK. At 100% coverage for all changes. There's one line that's not covered in cedar_unmarshal that is unrelated to my changes. I tried to figure out a quick way to reach it, but failed. :)

Copy link
Collaborator

@patjakdev patjakdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good to me. Just a couple of nits.

if err != nil {
return zeroValue(), err
}
ok, err := lhs.LessThanOrEqual(rhs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: the ok here maybe should be isLessThanOrEqual? ok implies something else to me.

MillisPerDay = int64(1000 * 60 * 60 * 24)
MillisPerHour = int64(1000 * 60 * 60)
MillisPerMinute = int64(1000 * 60)
MillisPerSecond = int64(1000)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: seems like these could be composed from each other


// A Duration is a value representing a span of time in milliseconds.
type Duration struct {
Value int64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should strongly consider making Value private. The accessor methods with units seem like the right public interface for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed. Note that Decimal still exposes this.

// Datetime represents a Cedar datetime value
type Datetime struct {
// Value is a timestamp in milliseconds
Value int64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should strongly consider keeping Value private and making an accessor method that gives you the time since the epoch in milliseconds.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed. Note that Decimal still exposes this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Decimal will take a bit more effort, I've made an issue in the IDX project for us to handle that in a separate PR.

- Lesser becomes ComparableValue
- Move ComparableValue to evalers only
- Move all magic values to constants
- TypeError when incompatible comparable types
- Support more deserialization for duration/datetime
- Make the datetime parser easier to follow
- Drop UnsafeDatetime in favor of FromStdTime(time.UnixMilli(..))
- Document methods
- Test Coverage to 100%

Signed-off-by: Andrew Gwozdziewycz <[email protected]>
@apg apg merged commit d1f59f4 into cedar-policy:main Sep 19, 2024
3 checks passed
@apg apg deleted the apg/po-515-add-datetime branch September 19, 2024 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants