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

Hash not unique #2309

Closed
jmooring opened this issue Jan 27, 2025 · 3 comments · Fixed by #2314
Closed

Hash not unique #2309

jmooring opened this issue Jan 27, 2025 · 3 comments · Fixed by #2314

Comments

@jmooring
Copy link

jmooring commented Jan 27, 2025

With the exception of my comment, below is a verbatim copy of https://github.com/terrastruct/d2/blob/master/docs/examples/lib/1-d2lib/d2lib.go:

d2lib.go
package main

import (
	"context"
	"os"
	"path/filepath"

	"oss.terrastruct.com/d2/d2graph"
	"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
	"oss.terrastruct.com/d2/d2lib"
	"oss.terrastruct.com/d2/d2renderers/d2svg"
	"oss.terrastruct.com/d2/d2themes/d2themescatalog"
	"oss.terrastruct.com/d2/lib/log"
	"oss.terrastruct.com/d2/lib/textmeasure"
	"oss.terrastruct.com/util-go/go2"
)

// Remember to add if err != nil checks in production.
func main() {
	ruler, _ := textmeasure.NewRuler()
	layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
		return d2dagrelayout.DefaultLayout, nil
	}
	renderOpts := &d2svg.RenderOpts{
		Pad:     go2.Pointer(int64(5)),
		ThemeID: &d2themescatalog.GrapeSoda.ID,
	}
	compileOpts := &d2lib.CompileOptions{
		LayoutResolver: layoutResolver,
		Ruler:          ruler,
	}
	ctx := log.WithDefault(context.Background())
	diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)

	// jmooring
	//
	// At this point diagram.Config is nil. Then we call d2svg.Render, which
	// calls diagram.HashID(), which calls diagram.Bytes(), which builds a byte
	// slice that includes diagram.Config if isn't nil.
	//
	// The result is that our hash is not unique when the same diagram markup
	// is rendered with different options in the same HTML file. This causes,
	// for example, one diagram to take on another diagram's style.

	out, _ := d2svg.Render(diagram, renderOpts)
	_ = os.WriteFile(filepath.Join("out.svg"), out, 0o600)
}

I suppose we could manually populate diagram.Config at this point, but it seems like that should have happened already.

@alixander
Copy link
Collaborator

It shouldn't be nil after d2lib.Compile?: https://github.com/alixander/d2/blob/4c74a05705a16a613bb11fb00a1b0b460d82b074/d2lib/d2.go#L80

I can also inspect the hash of the resulting diagram on playground (using DOM inspector) with and without some config, and see that it's different.

https://play.d2lang.com/?script=KkssKrZSqOZSUEgx0k3Oz0vLTIdwFRRKMlJzU3UzU6wUjA0MuBQUarlquSoUdO0UKrkAAQAA__8%3D&

@jmooring
Copy link
Author

jmooring commented Jan 29, 2025

I just tested this again with 4c74a05. Please run this example:

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"oss.terrastruct.com/d2/d2graph"
	"oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
	"oss.terrastruct.com/d2/d2lib"
	"oss.terrastruct.com/d2/d2renderers/d2svg"
	"oss.terrastruct.com/d2/d2themes/d2themescatalog"
	"oss.terrastruct.com/d2/lib/log"
	"oss.terrastruct.com/d2/lib/textmeasure"
	"oss.terrastruct.com/util-go/go2"
)

// Remember to add if err != nil checks in production.
func main() {
	ruler, _ := textmeasure.NewRuler()
	layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
		return d2dagrelayout.DefaultLayout, nil
	}
	renderOpts := &d2svg.RenderOpts{
		Pad:     go2.Pointer(int64(5)),
		ThemeID: &d2themescatalog.GrapeSoda.ID,
	}
	compileOpts := &d2lib.CompileOptions{
		LayoutResolver: layoutResolver,
		Ruler:          ruler,
	}

	ctx := log.WithDefault(context.Background())

	diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
	fmt.Printf("%+v\n", diagram.Config) // prints <nil>
	out, _ := d2svg.Render(diagram, renderOpts)
	_ = os.WriteFile(filepath.Join("grape_soda.svg"), out, 0o600)

	renderOpts.ThemeID = &d2themescatalog.EarthTones.ID

	diagram, _, _ = d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
	fmt.Printf("%+v\n", diagram.Config) // prints <nil>
	out, _ = d2svg.Render(diagram, renderOpts)
	_ = os.WriteFile(filepath.Join("earth_tones.svg"), out, 0o600)
}

Both the resulting SVG files have class .d2-1706184380. That's fine when they're individual files, but not when they're embedded in the same HTML file.

Also note that in the above, fmt.Printf("%+v\n", diagram.Config) prints <nil>.

If I manually set diagram.Config ...

	diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
	fmt.Printf("%+v\n", diagram.Config) // prints <nil>
	diagram.Config = &d2target.Config{
		ThemeID: &d2themescatalog.GrapeSoda.ID,
	}
	out, _ := d2svg.Render(diagram, renderOpts)
	_ = os.WriteFile(filepath.Join("grape_soda.svg"), out, 0o600)

	renderOpts.ThemeID = &d2themescatalog.EarthTones.ID

	diagram, _, _ = d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
	fmt.Printf("%+v\n", diagram.Config) // prints <nil>
	diagram.Config = &d2target.Config{
		ThemeID: &d2themescatalog.EarthTones.ID,
	}
	out, _ = d2svg.Render(diagram, renderOpts)
	_ = os.WriteFile(filepath.Join("earth_tones.svg"), out, 0o600)

...the classes in the two files are different, as they should be.

@alixander
Copy link
Collaborator

alixander commented Jan 29, 2025

Oh I see now.

That config being nil is expected, as it represents config defined in the DSL.

But changing the render opts should've resulted in a different diagram hash. Fixed here: #2314

Thank you for reporting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

2 participants