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

Headers improvements #23

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

antoniyatrifonova
Copy link
Contributor

  • timeout is changed to time.Duration
  • usage of 'comma ok' idiom
  • add constant for ditto specific content type

Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io

- timeout is changed to time.Duration
- usage of 'comma ok' idiom
- add constant for ditto specific content type

Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
@antoniyatrifonova
Copy link
Contributor Author

@konstantina-gramatova please review this PR

- Update the doc according the refactoring
- Convert timeout in time.Duration after unmarshaling JSON
- Add more tests scenarios

Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
Comment on lines 80 to 103
func parseTimeout(timeout string) (time.Duration, error) {
l := len(timeout)
if l > 0 {
t := time.Duration(-1)
switch timeout[l-1] {
case 'm':
if i, err := strconv.Atoi(timeout[:l-1]); err == nil {
t = time.Duration(i) * time.Minute
}
case 's':
if timeout[l-2] == 'm' {
if i, err := strconv.Atoi(timeout[:l-2]); err == nil {
t = time.Duration(i) * time.Millisecond
}
} else {
if i, err := strconv.Atoi(timeout[:l-1]); err == nil {
t = time.Duration(i) * time.Second
}
}
default:
if i, err := strconv.Atoi(timeout); err == nil {
t = time.Duration(i) * time.Second
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a specific reasoning behind implementing a custom duration string parsing instead of using the one from the Go SDK - i.e. time.ParseDuration?

Comment on lines 111 to 113
func inTimeoutRange(timeout time.Duration) bool {
return timeout >= 0 && timeout < time.Hour
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If we are about to perform header values validation here - are we about to introduce them for all known headers limitations and expected values - e.g. requested-acks header value validation, etc.? Otherwise, it is not really consistent.

Comment on lines 76 to 77
}
return 60 * time.Second
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the client supposed to handle default values of the headers? Or by spec, the cloud backend is supposed to interpret missing values to their defaults?
In general, the aim is always to send the smallest possible payload from the device - i.e. omit defaulting in this case.
Another topic is that if we add this default values handling here - it should be added everywhere for consistency. Otherwise - it's hard to use if it's randomly implemented.

Choose a reason for hiding this comment

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

In this case the value is only returned/read and not set explicitly.

"In general, the aim is always to send the smallest possible payload from the device - i.e. omit defaulting in this case."

I'm totally agree with this statement, so it's good all getters/readers to return the default value no matter it's explicitly set or not (as it's done for the "response-required" value).
In order to make it consistent (and keep the payload compact), the setters/modifiers should remove the header if it is with its default value, i.e. no need to check a local variable before calling WithResponseRequired(required) - the Lib will do this for me.

For advanced users the Values map is exported so they can check any header existence or set it with its default value.

Comment on lines 71 to 75
func (h *Headers) Timeout() time.Duration {
if value, ok := h.Values[HeaderTimeout]; ok {
if duration, err := parseTimeout(value.(string)); err == nil {
return duration
}
Copy link
Contributor

Choose a reason for hiding this comment

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

If we decide to change the API from string to time.Duration - the value in the Headers.Values map must be of type time.Duration as well - not a string one parsed every time. Otherwise, we introduce an inconsistency.
This of course, will requires some marshaling/unmarshaling customizations but it's important to keep the usage of the header the same regardless how the value was accessed - via the getter or via the Headers.Values map directly.

Trifonova.Antonia added 2 commits March 30, 2022 15:52
- change header type to map
- provide the functionality for case-insensitive
- change timeout getter and setter method to expect time.Duration type
- improve the getter methods to return default value (if this value is presented)
- improve the test scenarios

Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
@@ -90,10 +94,10 @@ func (h Headers) CorrelationID() string {
return v.(string)
}
}
return ""
return uuid.New().String()
Copy link
Contributor

Choose a reason for hiding this comment

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

If initially missing, on every invocation of CorrelationID() a newly generated id would be returned.
Also if there are more than one headers differing only in capitalization, should we make sure that the one that is initially returned(the first met one) would be returned on every next invocation as well?

// See https://www.eclipse.org/ditto/protocol-specification.html
type Headers map[string]interface{}

// CorrelationID returns the 'correlation-id' header value if it is presented.
// If the header value is not presented, the CorrelationID returns empty string.
// If the header value is not presented, the 'correlation-id' header value will be generated in UUID format.
//
// If there are two headers differing only in capitalization CorrelationID returns the first value.
Copy link
Contributor

Choose a reason for hiding this comment

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

If there are more than one headers differing only in capitalization CorrelationID returns the first met value.

if err := json.Unmarshal(data, &temp); err != nil {
return err
}
for k := range temp {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this cleaning needed? In the generic case the initial headers are empty.

func WithGeneric(headerID string, value interface{}) HeaderOpt {
return func(headers Headers) error {
for k := range headers {
if strings.EqualFold(k, headerID) {
delete(headers, k)
Copy link
Contributor

Choose a reason for hiding this comment

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

The user might want to add several different headers with keys only differing in the capitalization. In this case the user should directly use the exported map?

Trifonova.Antonia added 3 commits April 8, 2022 09:18
Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
//
// If there are more than one headers differing only in capitalization, the CorrelationID returns the first met value.
// To use the provided key to get the value, access the map directly.
func (h Headers) CorrelationID() (string, bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Return only the correlation id, skip boolean flag for the unexpected type. Keep it consistent with other methods.

func sortHeadersKey(h Headers) []string {
var keys []string
for k := range h {
keys = append(keys, k)
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be more efficient to allocate the []string in advance as the size is known, than using append.

}

// With sets new Headers to the existing.
func (h Headers) With(opts ...HeaderOpt) Headers {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove it.

// ContentType returns the 'content-type' header value if it is presented.
//
// If the header value is not presented, the ContentType returns the empty string.
// If the header value is not presented, the ContentType returns the empty string.
Copy link
Contributor

Choose a reason for hiding this comment

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

The line duplicated, copy paste error.

// If the header value is presented, but the type is not a string or the value is not valid, the Timeout returns the default value.
//
// If there are more than one headers differing only in capitalization, the Timeout returns the first met value.
// To use the provided key to get the value, access the map directly.
Copy link
Contributor

Choose a reason for hiding this comment

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

The API doc might be more optimized and punctual.

// If there aren't any headers for 'correlation-id', WithCorrelationID sets a new header with
// key 'correlation-id' and the provided value.
//
// To use the provided key to set a new value, access the map directly.
Copy link
Contributor

Choose a reason for hiding this comment

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

The API doc might be more optimized and punctual.

Signed-off-by: Antonia Trifonova antonia.trifonova@bosch.io
Copy link
Contributor

@dimitar-dimitrow dimitar-dimitrow left a comment

Choose a reason for hiding this comment

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

LGTM

@thjaeckle
Copy link
Member

Can/should this be merged? With most of the commit messages containing (only) a "PoC" message?

@konstantina-gramatova
Copy link
Contributor

It's undergoing a last review currently - will leave a comment to confirm merge-readiness.

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.

None yet

5 participants